Angular: ExpressionChangedAfterItHasBeenCheckedError: تم تغيير التعبير بعد التحقق منه في ng 4

تم إنشاؤها على ١٦ يونيو ٢٠١٧  ·  201تعليقات  ·  مصدر: angular/angular

أقوم بإرسال ...


[ ] Regression (behavior that used to work and stopped working in a new release)
[X ] Bug report #14748 
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

السلوك الحالي


بعد استخدام كائن يمكن ملاحظته في القالب الخاص بي باستخدام غير متزامن ، أتلقى:
خطأ خطأ: ExpressionChangedAfterItHasBeenCheckedError: تم تغيير التعبير بعد التحقق منه. القيمة السابقه: ''. القيمة الحالية: "شيء"

سلوك متوقع

من فضلك أخبرنا عن بيئتك

<br i="15"/>
Angular version: 4.2.2

<p i="16">Browser:</p>

<ul i="17">
<li i="18">[X] Chrome (desktop) version Version 58.0.3029.110 (64-bit)</li>
</ul>

<p i="19">For Tooling issues:</p>

<ul i="20">
<li i="21">Node version: v6.10.3</li>
<li i="22">Platform: Mac</li>
</ul>
core regression bufix

التعليق الأكثر فائدة

أحصل على نفس المشكلة بعد الترقية من 4.1.3 إلى 4.2.3. يؤدي استخدام setTimeout إلى حل المشكلة.

من عند:

PanictUtil.getRequestObservable (). Subscribe (data => this.requesting = data) ؛

إلى:

PanictUtil.getRequestObservable (). Subscribe (data => setTimeout (() => this.requesting = data، 0)) ؛

ال 201 كومينتر

واجهت نفس المشكلة مع استخدام الإعادة. إليك رابط المشكلة التي نشرتها على المستودع الخاص بهم. عندما لا توجد أخطاء في الزاوية 2 ولكن في الزاوية 4.2.2 يتم تغيير التعبير بعد التحقق من الأخطاء.
https://github.com/angular-redux/store/issues/428

تظهر المشكلة بالنسبة لي على مكوناتي عند الاصطدام من 4.1.3 إلى 4.2.x. العودة إلى الإصدار 4.1.3 يعمل على إصلاح المشكلة بالنسبة لي ، ولكن هذا ليس شيئًا أريد الاحتفاظ به إلى الأبد.

يمكنني التكاثر في مشروع بسيط ، ما زلت أحاول معرفة كيفية إصلاح هذا الانحدار (ربما). لكن يبدو أن الشيك القذر قد تم فرضه في ديف. الوضع من 4.2.x. لكن التغيير ليس واضحًا بشأن ذلك. نرحب بأي إكرامية.

أحصل على نفس المشكلة بعد الترقية من 4.1.3 إلى 4.2.3. يؤدي استخدام setTimeout إلى حل المشكلة.

من عند:

PanictUtil.getRequestObservable (). Subscribe (data => this.requesting = data) ؛

إلى:

PanictUtil.getRequestObservable (). Subscribe (data => setTimeout (() => this.requesting = data، 0)) ؛

jingglang ، هل يمكنك محاولة إعادة إنتاج المشكلة على http://plnkr.co/edit/tpl : AvJOMERrnz94ekVua0u5 بأقل قدر ممكن من الكود؟

أواجه نفس المشكلة - تمت الترقية من 2.4 إلى 4.2 ، وبعض منطق الإخفاء / العرض الخاص بي معطل الآن. إنه منطق على غرار الأكورديون ، حيث تعتمد الرؤية على مقارنة اللوحة الحالية بلوحة يمكن ملاحظتها تحمل أي لوحة يجب أن تكون مرئية (من بين أشياء أخرى). يستخدم المكون select للحصول على ما يمكن ملاحظته ، ويكون مرتبطًا بـ | غير متزامن في القالب - عمل مثل السحر في 2.4 ، احصل الآن على ExpressionChangedAfterItHasBeenCheckedError والألواح لا تخفي وتظهر عند النقرة الأولى ، فقط في الثانية.

أحصل على نفس المشكلة عند التحديث 4.1.3 إلى 4.2.2.

لكني وجدت حلاً.
حقن ChangeDetectionRef ، واستدعاء الوظيفة detectionChanges() عند نقطة الخطأ.

constructor(private cdr: ChangeDetectionRef) {
}

onChange(): void {
  this.val += 1; // <- get error after
  this.cdr.detectionChanges();
}

أعتقد أن هذه مشكلة تتعلق بتغيير المتابعة الذي تم إجراؤه في 4.2.

https://github.com/angular/angular/pull/16592

لحل هذه المشكلة: https://github.com/angular/angular/issues/14321

هل لديك أي محتوى داخل علامة ng-content ؟

لدي نفس المشكلة مع نموذج داخل علامة ng-content والتي تظهر الآن أخطاء مع خطأ ContentChangedAfter.

أحصل على نفس المشكلة عند التحديث 4.1.3 إلى 4.2.0 أو أعلى

أرفق مشروعًا صغيرًا لإعادة إنتاج الخطأ

app-error.zip

بعد ذلك ، قمت بخفض التصنيف إلى 4.1.3 (كما هو مذكور أعلاه) واختفى الخطأ ، لذا مهما كان التعارض ، فهو محدد بـ 4.2. ليس لدي الوقت لتجميع plunkr هذا الأسبوع (أو التالي) ، ولكن يبدو أنه مرتبط بإجراء واحد لتحديث ملاحظتين منفصلتين في المتجر ، ولكل منهما تأثير على واجهة المستخدم. يحدث أحد تحديثات واجهة المستخدم ، بينما يتسبب الآخر في الاستثناء.

هممم ،
من المؤكد أن العودة إلى الإصدار 4.1.3 يحل المشكلة ويطبق أيضًا مهلة كما هو موضح بواسطةjingglang.

مرحباtytskyi. لقد قمت بعمل plunkr http://plnkr.co/edit/XAxNoV5UcEJOvsAbeLHT لهذه المشكلة. آمل أن يساعد.

الحل البديل المقدم من jingglang يعمل (بالنسبة لي) على 4.2.0 أو أعلى
كما أن تغيير حدث الانبعاث الخاص بالطفل إلى المُنشئ الخاص به لا يؤدي إلى ظهور الخطأ بعد الآن

umens تقوم بتحديث المكون الرئيسي بعد التحقق منه وبالتالي لا تحتفظ بتدفق بيانات أحادي الاتجاه

alexzuza كيف يمكنني تحقيق ذلك؟ لطالما فعلت ذلك بهذه الطريقة دون خطأ. لماذا لا يؤدي وضع SetTimout إلى ظهور الخطأ بعد الآن. (لقد قمت بتحديث plunkr) أشك في أنها تتداخل مع دورة الحياة أم أنها؟

مرحبا umens شكرا لك على وقتك. المشكلة هي ما يقوله alexzuza : تقوم بتحديث حقل المكون الأصلي بعد التحقق منه. الكود الخاص بك يكافئ تقريبًا هذا: http://plnkr.co/edit/ksOdACtXScZKw3VRAYTm؟p=preview (لاحظ أنني قمت بإزالة الخدمة لتقليل الرمز).
لماذا عملت؟ ربما عن طريق الصدفة كانت النسخة القديمة من الزاوي أو Rxjs علة هنا. هل يمكنك معرفة الإصدارات التي تم استخدامها؟ أو حتى وضعها في مكبس العمل؟

لماذا لا يؤدي وضع SetTimout إلى ظهور الخطأ بعد الآن.

لأنك تقوم بتغيير الحقل بشكل غير متزامن ، والذي يحترم تدفق البيانات أحادي الاتجاه.

فلننظر إلى الأمر من العكس: لماذا يظهر الخطأ؟ الفحص الأول لاكتشاف التغيير ينتقل من أعلى إلى أسفل. يتحقق من app.toolsConfig المرتبطة بالقالب. ثم يعرض <child-cmp> والذي يقوم بتحديث app.toolsConfig بشكل متزامن . تم التقديم. يتم الآن تشغيل الفحص الثاني (وضع dev فقط) لـ Change Detection للتأكد من أن حالة التطبيق مستقرة. لكن app.toolsConfig قبل تقديم الطفل لا يساوي app.toolsConfig بعده. كل هذا يحدث خلال "دورة" واحدة ، "دورة" لاكتشاف التغيير.

حسنا. شكرا لك على الإجابة التفصيلية. لم أتمكن من العثور على معلومات حول وقت حدوث دورة حياة المكون الفرعي.
لإصدار "العمل" السابق الذي استخدمته:
@ angular / *: 4.1.1 (باستثناء compiler-cli -> 4.1.0)
rxjs: 5.3.1

umens هنا هو خطأ تتكرر مع الإصدارات القديمة التي ذكرتها: http://plnkr.co/edit/kfoKmigXzFXwOGb2wyT1؟p=preview. إنه يلقي ، لذلك ربما يؤثر بعض التبعية على سلوك طرف ثالث أو لا يكتمل تعليمات التكاثر؟

لا استطيع ان اقول.
ها هو yarn.lock الخاص بي إذا كنت مهتمًا بالمضي قدمًا: https://pastebin.com/msARLta1
لكني لا أعرف ما يمكن أن يكون مختلفًا

تظهر لي أخطاء "ExpressionChanged ..." مشابهة بعد التبديل من 4.1.2 إلى 4.2.3 .

في حالتي ، لدي مكونان يتفاعلان عبر خدمة. يتم تقديم الخدمة عبر الوحدة النمطية الرئيسية الخاصة بي ، ويتم إنشاء ComponentA قبل ComponentB .

في 4.1.2 ، عملت البنية التالية على ما يرام دون أخطاء:

قالب المكون:

<ul *ngIf="myService.showList">
...

فئة المكون ب:

ngOnInit() {
  this.myService.showList = false; //starts as true in the service
  ...
}

في 4.2.3 ، يجب علي تعديل قالب ComponentA النحو التالي لتجنب الخطأ "ExpressionChanged ...":

قالب المكون:

<ul [hidden]="!myService.showList">
...

ما زلت أحاول معرفة سبب تحول هذا الأمر إلى مشكلة فقط ، ولماذا التبديل من *ngIf إلى [hidden] يعمل.

لدي نفس المشكلة ، وتحدث غالبًا عندما أستخدم أنبوبًا غير متزامن مثل هذا:
<div>{{ (obs$ |async).someProperty }}</div>
إذا استخدمته على هذا النحو:

<div *ngIf="obs$ | async as o">
  <div>{{ o.someProperty }}</div>
</div>

ثم يختفي الخطأ ، لذلك لست متأكدًا من أن السبب هو فقط لأن الكثير من الأشخاص ارتكبوا خطأ لم يتم اكتشافه من قبل: أعتقد أنه تم إدخال خطأ في 4.2 ...

نفس المشكلة بالنسبة لي. يبدو أن المشكلة تأتي من @ angular / router

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

<span class="glyphicon pull-right" [ngClass]="{'glyphicon-chevron-up' : (ui$ | async)?.visiblePanels[VisiblePanel.IngredientTypes], 'glyphicon-chevron-down' : !((ui$ | async)?.visiblePanels[VisiblePanel.IngredientTypes])}"></span>

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

مثل acidghost ، يمكنني حل هذه المشكلة عن طريق تشغيل ChangeDetectorRef#detectChanges في AfterViewInit من المكون الرئيسي.

إذا كان لديك plunkr repro ، فأنا على استعداد للتحقق من ذلك لأن لدينا بعض الشكاوى المماثلة على gitter وكان الأمر دائمًا يمثل مشكلة للمستخدم.

لا يمكن التأكد مما إذا كان الخلل قد تم تقديمه في الإصدار 4.2 أو ما إذا كان الخطأ هو بالأحرى عدم وجود خطأ في <4.2 وتم حله في 4.2.

خطأ: ExpressionChangedAfterItHasBeenCheckedError
المحلول:
componentRef.changeDetectorRef.detectChanges () ،

يوجد أدناه جزء من الكود الخاص بي (مكونات تم إنشاؤها ديناميكيًا):
componentRef = viewContainerRef.createComponent (componentFactory) ، تم إنشاء // المكون
(componentRef.instance) .data = input_data ؛ // تغيير بيانات المكون يدويًا هنا
componentRef.changeDetectorRef.detectChanges () ، // سيؤدي إلى اكتشاف التغيير

لمكونات بسيطة فقط جوجل كيفية الوصول إلى "componentRef" ثم التنفيذ
componentRef.changeDetectorRef.detectChanges () ،
بعد ضبط / تغيير بيانات المكون / النموذج.

الحل 2:
تلقيت هذا الخطأ مرة أخرى أثناء فتح مربع حوار "this.dialog.open (DialogComponent)".
لذا فإن وضع رمز الحوار المفتوح هذا داخل دالة ثم تطبيق setTimeout () على تلك الوظيفة أدى إلى حل المشكلة.
مثال:
openDialog () {
اسمح panelRef = this.dialog.open (DialogComponent) ؛
}
ngOnInit (): باطل {
setTimeout (() => this.openDialog ()، 0) ؛
}
على الرغم من أنه يبدو مثل الاختراق .. يعمل !!!

منذ 4.2 لدي نفس المشكلة ولم ينجح أي حل أو حل بديل بالنسبة لي: (أقوم بإنشاء مكون بشكل ديناميكي مع مصنع مكون في طريقة ngAfterViewInit. يستخدم هذا المكون توجيهًا يحتوي على حقل @Input() . أنا ربط هذا إلى سلسلة ثابتة. يبدو أن الحقل @Input() للتوجيه غير محدد حتى يتم إنشاء عرض المكون والتحقق منه وبعد ذلك فقط تهيئته بالسلسلة الثابتة.

https://plnkr.co/edit/E7wBQXm8CVnYypuUrPZd؟p=info

staticComponent.ts

export class StaticComponent implements AfterViewInit {
  @ViewChild('dynamicContent', {read: ViewContainerRef})
  dynamicContent: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  ngAfterViewInit(): void {
    const componentFactory: ComponentFactory<DynamicComponent> = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
    this.dynamicContent.createComponent(componentFactory);
  }
}

test.directive.ts

@Directive({
  selector: '[testDirective]'
})
export class TestDirective {
  @Input()
  testDirective: string;
}

dynamic.component.ts

@Component({
  selector: 'dynamic-component',
  template: `<div [testDirective]="'test'">XXX</div>`
})
export class DynamicComponent {
}

خطأ خطأ: ExpressionChangedAfterItHasBeenCheckedError: تم تغيير التعبير بعد التحقق منه. القيمة السابقة: "غير محدد". القيمة الحالية: "اختبار". يبدو أن العرض قد تم إنشاؤه بعد أن تم التحقق من اتساخ والديه وأطفاله. هل تم إنشاؤه في خطاف اكتشاف التغيير؟

أنا مرتبك قليلاً في قراءة هذا الموضوع. هل هذا خطأ معروف سيتم حله في إصدار قادم؟ أم أن هذا السلوك متوقع؟ إذا كان الأمر كذلك ، فما الحل؟ شكرا لك.

لا توجد فكرة عما إذا كانت مرتبطة أم لا ، لكني أكره هذه الرسالة بشغف! يظهر هذا فقط بالنسبة لي عند اختبار الوحدة باستخدام أمر "اختبار ng" خارج الصندوق. أفترض أنه قد يتم الخلط بيني وبين كيفية السخرية بنجاح من خدمة بسيطة. مندهشًا أيضًا من عدم رؤية أي شخص آخر لهذا في الاختبار حتى الآن ، ولم يتمكن من العثور على أي اختبار متعلق به. سأشاهد هذا الموضوع وأي بحث عن ExpressionChangedAfterItHasBeenCheckedError + اختبار الوحدة.

كنت أحاول تجميع plnkr ، ووجدت أنه من الصعب جدًا إعادة إنتاج الخطأ - ومع ذلك ، أعتقد أن تصحيح الأخطاء الشامل جعلني أقرب قليلاً إلى رؤية الخطأ الذي يحدث (على الرغم من عدم تقرير ما إذا كان خطأ ، أو خطأ من جانبي).

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

يدير مخفض واجهة المستخدم الخاصة بي الرؤية ، وهو مصدر تغيير الحالة الذي يتسبب في حدوث الخطأ. فيما يلي نسخة مختصرة من علبة التروس ، توضح الإجراءات ذات الصلة:


export const userInterfaceReducer = (state = INITIAL_UI(), action: any) => {
    switch (action.type) {
        case IngredientActions.LIST_INGREDIENTS:
            if (action.hideTypes) {
                return Object.assign(state, { visiblePanel: VisiblePanel.Ingredients });
            }
            return state;
        default:
            return state;
    }
}

عندما أقوم بتشغيل هذا في تطبيق الاختبار الصغير الخاص بي ، فإنه يعمل - أولاً ، يتم تشغيل الإجراء LIST_INGREDIENTS ويتم إرجاع الحالة الجديدة ؛ ثم ينطلق الإجراء @ angular-redux / router :: UPDATE_LOCATION ، ويتم ضرب الحالة الافتراضية ، مع إعادة الحالة نفسها.

عندما أقوم بتشغيله في التطبيق الفعلي ، يتم عكس الترتيب أو الإجراءات. @ angular-redux / router :: يتم تشغيل إجراء UPDATE_LOCATION أولاً ، ويعيد الحالة القديمة من الوضع الافتراضي. ثم حرائق LIST_INGREDIENTS ، وإرجاع الحالة الجديدة (المتغيرة) - ويظهر الخطأ.

أنا منفتح تمامًا على فكرة أنني فعلت شيئًا غبيًا وهذا ليس خطأً فعليًا - ولكن إذا كان الأمر كذلك ، فهل يرى أي شخص آخر ما قمت به؟

(كملاحظة سفلية ، راجعت الإصدار 4.1.3 ... كما أنه يطلق مخفضات إعادة الإرسال بالترتيب "الصحيح" - أي أن تغيير الموقع ينطلق بعد إجراء مكونات القائمة ، وهو ما يفترض أنه يعمل ، ولكن الإصدار 4.2 لا).

المعاناة من نفس الخطأ المزعج عندما يقوم أحد المكونات بتغيير خاصية الخدمة. تم الحل بنقل * ngIf إلى المكون الرئيسي. لذلك فهو بالتأكيد خطأ

كان قادرًا على حل هذه المشكلة باستخدام طريقة ngDoCheck () واستدعاء طريقة ChangeDetectorRef.detectChanges.

public ngDoCheck(): void {
    this.cdr.detectChanges();
}

pappacurds لا تفعل ذلك. لقد تسببت في دورات لا نهائية لاكتشاف التغيير مع ذلك.

قد يكون نفس الخطأ هنا https://plnkr.co/edit/IeHrTX0qil17JK3P4GBb؟

الخطأ: previous undefined after undefiend

بعد التحديث نفس الخطأ

نفس الشيء بالنسبة لي

كل شيء على ما يرام في 4.0.0 ، بعد الترقية إلى 4.2.6 أحصل على نفس الخطأ.

تشرح المقالة كل ما تحتاج لمعرفته حول الخطأ ExpressionChangedAfterItHasBeenCheckedError بالتفصيل سبب حدوث هذا الخطأ والإصلاحات الممكنة

الإصلاحات واضحة ، ولكن تغيير الإطار بطريقة تجعل التحديث يتسبب في إعادة كتابة تطبيقات المستخدم من أجل "دعم" تحديث النظام الأساسي الثانوي الجديد. JS لا تزال تحتفظ بالأخطاء القديمة للحفاظ على التوافق ...

نفس الخطأ هنا

أرى أيضًا هذه المشكلة في ~4.2.4 . أدى الرجوع إلى ~4.1.3 حل المشكلة.

المشكلة تتعلق بتغيير قيم ContentChildren ، حتى عند استخدام ChangeDetectorRef detectChanges() في نهاية AfterViewInit للوالد (حيث كان يجري التغييرات). الغريب أن هذه المشكلة تحدث فقط مع المكونات وليس التوجيهات. لقد قمت بتحويل التوجيه الذي كنت أعمل به في 4.2.4 ليكون مكونًا بقالب <ng-content></ng-content> ثم بدأ في طرح الخطأ ExpressionChangedAfterItHasBeenCheckedError .

حصلت على هذا أثناء العمل مع التحكم في إدخال المادة 2. حاولت تحديد قيمة ذلك

ngAfterViewInit(): void {
this.numberFormControl.setValue(200);
}

لقد قمت بتجميع Plunker البسيط الذي يعرض حالة حيث بدأت أخطاء ExpressionChangedAfterItHasBeenCheckedError فقط في الظهور بدءًا من Angular 4.2.x .

لقد قمت بإنشاء مكبس بسيط يعرض الخطأ عند تغيير قيمة ContentChild حتى عند استخدام ChangeDetectorRef detectChanges() في المكون الرئيسي ngAfterViewInit .

تحرير: تم أيضًا إنشاء plunker بسيطًا استنادًا إلى المثال الأول الخاص بي ولكن مع التوجيه الأصلي بدلاً من المكون وبدون خطأ.

saverett أشاهد رسالة خطأ مختلفة في موقع

JSMike كان لدي نفس الحالة تمامًا كما في plunker (تم حل المشكلة من خلال التفاف الرمز الذي يشير إلى ContentChild in setTimeout ، plunker )

JSMike شكرا على

matkokvesic هذا ليس حلاً حقًا ، إنه يتسبب فقط في حدوث التغيير خارج خطاف دورة الحياة. لا يتسبب مثال الرمز المقدم في حدوث خطأ عند استخدام Angular v4.1.3

JSMike أوافق ، إنه حل مؤقت. استخدام العناصر المشار إليها بواسطة ContentChildren في ngAfterViewInit كان يعمل بشكل جيد قبل Angular 4.2.6.

لقد أصلحته باستخدام changeDetection: ChangeDetectionStrategy.OnPush و this.changeDetector.markForCheck(); في الكود الخاص بي

touqeershafi إذا كنت تستخدم this.changeDetector.markForCheck(); فهذا لا يعني أنك لا تستخدم الأشياء بالطريقة الصحيحة

لا يزال هذا يحدث. هذا بالتأكيد خطأ ، وليس سلوكًا متوقعًا ويجب إصلاحه.

istiti أتفهم أن هذه ليست طريقة مناسبة ولكن على الأقل يمكنك التخلص من هذا الخطأ مؤقتًا ، ولكن بقدر ما يتعلق TODO مع عنوان url للخطأ.

لا يزال هذا يحدث. هذا بالتأكيد خطأ ، وليس سلوكًا متوقعًا ويجب إصلاحه.

أعتقد أنني يجب أن أتفق مع rwngallego ... قبل التحديث 4.2.x ، كنت قد كتبت بالفعل عددًا من مكونات stlye القابلة لإعادة الاستخدام / ng-content والتي تعتمد على ViewChildren و ContentChildren وإنشاء علاقات بين الوالدين والطفل من خلال هذه القنوات (الحصول على الخصائص وما إلى ذلك).

أجد نفسي مضطرًا باستمرار إلى حقن مثيل ChangeDetectorRef في كل هذه المنشئات (بما في ذلك الفئات الفرعية التي تمد هذه المكونات) ، ولا أشعر بأن السلوك المقصود أو الصحيح.

بالنسبة لأولئك الذين يستخدمون مكونًا ديناميكيًا مثل saverett (باستخدام جهاز التوجيه يستخدم مكونًا ديناميكيًا) ، أعتقد أن المشكلة مذكورة هنا https://github.com/angular/angular/issues/15634.

لأي شخص يقوم بتغييرات داخل ngAfterViewInit / ngAfterViewChecked أعتقد أنه من المتوقع.

بالنسبة للآخرين ، (لم أر أي شيء هنا بعد) لا يزال هذا لغزا بالنسبة لي.

هذا ما يحدث لي في السيناريو التالي.
لديك مكون أصلي يستخدم مكونًا فرعيًا. يحتاج المكون الفرعي إلى البيانات التي سيوفرها الأصل ولكن هذه البيانات تأتي من نتيجة واعدة في المنشئ الأصل. وبالتالي:

الأبوين

constructor {
dataservice.getData().then((result) => {
this.source = result;
});
}

نموذج الأصل

<app-child [source]="source"></app-child>

مكون تابع

@Input() source:any[];

هذا الرمز يلقي هذا الخطأ ولكن إضافة هذا إلى المكون الرئيسي حل المشكلة:

constructor(private cdRef: ChangeDetectorRef) {
dataservice.getData().then((result) => {
this.source = result;
 this.cdRef.detectChanges();
});
    }

الآن هل هذا حل موصى به أم أنه حل بديل لخلل وما الآثار المترتبة على إجراء مثل هذا التغيير؟ بالنسبة لي ، يبدو أنك تخبر Angular باكتشاف التغييرات التي أعتقد أنه شيء يجب عليك تجنبه ...

sulhome ... مشكلتك هي أن الوعد في مُنشئ الوالد يغير @Input على المكون الفرعي ... أثناء دورة القرص المضغوط وتأكيده في وضع dev. هذا هو سبب الخطأ.

يضيف السطر this.cdRef.detectChanges(); دورة القرص المضغوط الثانية لذا فإن التأكيد بعد ذلك يكون ناجحًا و => لا يوجد خطأ.

لذلك كل شيء يعمل كما هو متوقع. من المهم أن تفهم كيف يعمل استخدام التركيبات الصحيحة والمنطق العام في التعليمات البرمجية الخاصة بك لتجنب هذا النوع من المشاكل. يمكنك أن تقرأ عنها في الشرح العميق هنا: https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

@ mlc-mlapis أنت تقول إنني أقوم بذلك بشكل صحيح بإضافة this.cdRef.detectChanges(); وأن هذا ليس له أي آثار؟
فكرت من خلال الاستماع إلى ngOnChanges على الطفل ، ثم يجب أن أختار التغيير في الإدخال @ من الوالدين. أليس هذا هو النهج الصحيح للقيام بذلك؟ يرجى ملاحظة أنني فعلت ذلك وما زلت أتلقى الخطأ ، لكنني اعتقدت أن هذا هو ما صمم من أجله ngOnChanges

sulhome نعم ، this.cdRef.detectChanges(); هو ما يساعدك ولكن له أيضًا نتيجة أن لديك قرصان مضغوطان الآن ... لذلك يعتمد الأمر على كيفية ومكان استخدام استراتيجية OnPush على المكونات للتخلص من النفقات الزائدة دورة CD أخرى.

إذا كان صحيحًا أن dataservice.getData يأخذ بعض البيانات باستخدام http ، فمن المحتمل أن يكون الحل الأسهل هو الحصول على عنصر يمكن ملاحظته في تلك الخدمة والاشتراك فيه من المكون الفرعي. لذلك عندما يتم إرسال البيانات على الدفق يمكن للطفل أن يتفاعل معها.

ghetolay للسجل ، هناك بعض الحالات التي يكون فيها خطأ ، انظر على سبيل المثال # 18129 الذي فتحته.

لدي هذه المشكلة عندما أحصل على بيانات من المكون الفرعي باستخدامOutput.

في الصفحة الرئيسية للمكونات الأم

drag_second(trigger){ console.log("dragged222222"+trigger); if(trigger){ this.isactive=true; }else{ this.isactive=false; } }
و home.html
<div class="upper" [ngClass]="{'isactive':isactive}">

يتم تغيير isactive عند بدء السحب والسحب ....

رسالة الخطأ هي
ExpressionChangedAfterItHasBeenCheckedError: لقد تغير التعبير بعد التحقق منه. القيمة السابقة: "صحيح". القيمة الحالية: "خطأ".

أو هل هناك أي طرق أخرى يمكنني من خلالها تغيير / إضافة سمة فئة؟

نفس المشكلة في 4.2.x و 4.3.0 ولكن ليس 4.1.x.

يحدث الخطأ فقط في المسارات التي تستخدم ng-template .

الحل: وضع كل الكود في ngAfterViewInit في setTimeout حل المشكلة بالنسبة لي (شكرًا / أرصدة لـ jingglang).

public ngAfterViewInit(): void {
    setTimeout(() => {
        //my code
    });
}

Wernerson ، ألق نظرة على تعليق JSMike حول setTimeout. من المهم ملاحظة أنك في الواقع تقوم فقط بتغيير الرمز خارج خطاف دورة الحياة.

كانت مشكلتي أنني استخدمت ActivatedRoute 's params Observable لتعديل بعض الحالات في ngOnInit() . يبدو أن Router يصدر التزامن params ، لذلك يحدث التحديث أثناء القرص المضغوط.
الحل البسيط الخاص بي لهذا حاليًا هو:

  // !!! see delay(0)
  // results are consumed by async Pipe, where I also had the CD issue (null vs. undefined)
  this.results = this._activatedRoute.params.delay(0)
    .do(params => this.searchParameters = this._createSearchParameters(params))
    .switchMap(p => {
      let key = this.searchParameters.key();
      return this._searchResultCache.results.map(r => r.get(key));
    });

(أعلم أن هذا ليس أجمل رمز تفاعلي / وظيفي على الإطلاق ؛))
لذلك كان الحل هو انبعاث المعلمة delay(0) بواحد tick .

آمل أن أتمكن من مساعدة الآخرين الذين لديهم مشاكل مماثلة.
كما يتم الترحيب بأي مخاوف ، كيف أن الحل الخاص بي خاطئ أو خطير.

ما هي حالة هذه القضية؟ لا يزال يظهر في Angular 4.2.6.
هل هذه مشكلة في الإنتاج يبني؟ لأن هذا الخطأ سيحدث فقط في تصميمات التصحيح.

أكره كل الحلول مع تشغيل اكتشاف التغيير يدويًا ، أو حتى تشغيله في setTimeout () ...

setTimeout() غير فعال ، لأنه يتسبب في دورة جديدة للكشف عن التغيير في التطبيق بأكمله ، بينما detectChanges() يتحقق فقط من المكون الحالي AFAIK.

كان لدى فريقي سوء فهم أساسي لما هو ngOnInit وما هو غير ذلك. يقول المستندات

ngOnInit() : تهيئة التوجيه / المكوّن بعد عرض Angular أولاً للخصائص المرتبطة بالبيانات وتعيين خصائص الإدخال الخاصة بالتوجيه / المكون. يتم استدعاؤه مرة واحدة ، بعد ngOnChanges () الأول.

كنا مشتركين في خدماتنا (التي تتعقب حالة التطبيق) من ngOnInit . اشتراكاتنا هي سلوكيات متزامنة . يطلقون النار في وقت الاشتراك. لذلك "بعد Angular أولاً ... يعيّن خصائص إدخال التوجيه / المكون" ، كنا نغير القيم على الفور. إليك Plunker بسيطًا للغاية يوضح أن هذه مشكلة.

يستخدم المثال في Tour Of Heroes في النهاية http غير المتزامن ، وبالتالي لا يسبب مشاكل. أعتقد أن هذا هو مصدر ارتباكنا.

ما نقوم به الآن هو الاشتراك في حالاتنا المتزامنة من المُنشئ بدلاً من ذلك ، بحيث يتم تعيين قيم مكوناتنا على الفور. اقترح آخرون في هذا الموضوع بدء اكتشاف التغيير الثاني عبر المهلات ، أو تعيين القيم لاحقًا ، أو إخبار الزاوي صراحة بالقيام بذلك. سيؤدي القيام بذلك إلى منع ظهور الخطأ ، ولكن ربما يكون ذلك غير ضروري.

يجب قراءة المقالة التي شاركهاmaximusk : كل ما تحتاج لمعرفته حول الخطأ ExpressionChangedAfterItHasBeenCheckedError .

إذا لم يكن هناك شيء آخر ، فيجب أن يؤدي هذا الخيط إلى تحذير متعمق لكل هذا في مستندات الزاوية الرسمية.

يجب قراءة المقالة التي شاركهاmaximusk : كل ​​ما تحتاج لمعرفته حول خطأ ExpressionChangedAfterItHasBeenCheckedError.

شكرا لك

adamdport

هل هذا العمل في حالة استخدامViewChildrenContentChildren لتعديل خصائص الطفل من الوالدين، لأن الأطفال لا تتوفر حتى تم تنفيذ ngAfterViewInit) دورة حياة (؟

الرجاء تذكر أن جزءًا من هذه المشكلة هو خطأ مؤكد وليس خطأ المستخدم:
https://github.com/angular/angular/issues/15634

المشكلة saverett التي تمت معالجتها صحيحة ، باستخدام *ngIf تسبب في الخطأ.

*مشاهدة...

يمكن أن يحدث هذا إذا قمت بتمرير الكائنات إلى مربع حوار Angular Material ثم في مربع الحوار استخدمهم مباشرة بدلاً من القيام بـ this.myDataInput = Object.assign({}, dialogDataInput) . أي this.myDataInput = dialogDataInput; ثم فعل شيئًا مثل this.myDataInput.isDefault = !this.myDataInput.isDefault; . كما هو موضح أعلاه ، يقوم المكون الفرعي (مربع الحوار) بتغيير القيم التي يتم عرضها في عرض المكون الرئيسي.

استبدال السمة بـ [السمة]

هل يعرف أي شخص على وجه اليقين ما إذا كان هذا خطأ مقصودًا أم خطأ؟
أليس من الصحيح تحميل البيانات في ngOnInit؟ لأنه إذا قمت بنقل الكود إلى المُنشئ (التحميل من خلال ملاحظة) ، فلن يتم إلقاء الخطأ ...

تضمين التغريدة هل لديك * ngIf في html الخاص بك؟ احذر من أن الأشياء ستُعرض بعد انتهاء ngOnInit ، solong ، كل شيء داخل * ngIf لن يعمل وقد يؤدي إلى هذا الخطأ.

يجب إعادة صياغتها. باستخدام "ngIf" وعناصر التحكم المخصصة ، تحصل على رسالة الخطأ هذه. المهلة بالتأكيد ليست حلا.

@ j-nord إذا كنت تقرأ التعليقات ، فستعلم أن timeout غير مطلوب.

@ j-nord / dgroh لقد واجهت نفس الشيء. لقد كنت أستخدم ng-hint الذي يستخدم التوجيه ngIf ولا يمكنني التخلص من هذا الخطأ. هل تمكنت من تنفيذ حل بديل؟

Wernerson نعم ، جزء من هذه المشكلة خطأ مقصود أو خطأ: https://github.com/angular/angular/issues/15634

@ mawo87 عملت معي مع المخفي لأنها لا تزيل العنصر من DOM. بالنسبة لمتطلباتنا ، كان هذا جيدًا وكان أيضًا حلاً نظيفًا.

@ mawo87
أولا عليك أن تجد المتغير الذي تعنيه رسالة الخطأ. Angular Team : سيكون من الجيد وجود اسم المتغير في رسالة الخطأ. ليس من السهل العثور على أقنعة أكبر و Webpack. إذا وجدت الأمر حيث يحدث ، فسيساعدك الأمر الموجود في سطر المتابعة:

this.cdr.detectionChanges();

انظر المثال من اللون القرمزي 928 من 19 يونيو.

شكرا dgroh و @ j-nord.
@ j-nord كنت أبحث أيضًا في طريقة DetectionChanges ، لكنني قد لا أستخدم هذا الأسلوب كما في المقالة التالية التي نصحت بعدم استخدامها:
https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeenchecked-error-e3fd9ce7dbb4

في حالتي ، أستخدم خدمة PageTitle وأشترك فيها في المكون الرئيسي ، وبعد ذلك عندما يتم تحميل المكونات الفرعية في العرض ، يتم تعيين PageTitle في الخدمة ويحصل الوالد على القيمة ويعرضها عبر الاشتراك. نجح هذا بدون أي أخطاء في الإصدار 4.1.x ، ولكنه يلقي خطأ ExpressionChangedAfterItHasBeenCheckedError في 4.2x والإصدارات الأحدث.

لا يزال كل شيء يعمل كما هو متوقع ، ولا تظهر الأخطاء في الإصدارات المنشورة / الإنتاج (بسبب عدم وجود دورة ثانية للكشف عن التغيير) ، ولكن في التطوير يوجد الكثير من اللون الأحمر في وحدة التحكم الخاصة بي.

ما زلت غير واضح تمامًا بشأن الإصلاح المناسب (حتى على المدى القصير). اشتراكات المكونات الرئيسية الخاصة بي موجودة في المُنشئ ، وتحديثات المكونات الفرعية عبر الخدمة موجودة في ngOnInit.

لقد قرأت منشور "كل ما تريد معرفته عن ..." ، والذي كان مفيدًا ، ولكنه لا يقودني إلى الاعتقاد بأنني أفعل أي شيء بشكل غير صحيح ، ولا يقدم حلاً عمليًا للمشكلة.

alignsoft إذا قمت بتعيين عنوان الصفحة في المكونات الفرعية في ngOnInit ، فأنا أعتقد أن الخطأ صحيح ... راجع المقالة التي تم ربطها من قبل.

alignsoft أوافق ، كان هناك الكثير من النقاش حول هذا الخطأ ، ولكن بدون تعليمات حقيقية واضحة حول ما إذا كان صحيحًا أم لا ... هل هذا أكثر من "تحذير" للمطور حول المعايير وربط / معالجة البيانات؟

في حالتي الخاصة ، أنا أتعامل مع مكونات الوالدين / الأطفال باستخدام ViewChild و ContentChild ،

rolandoldengarm لقد حاولت الاشتراك في المُنشئ الأصل وتعيين القيم في AfterViewInit في المكونات الفرعية وما زلت أحصل على الخطأ. هل هذه هي الطريقة الصحيحة للتعامل مع هذا بناءً على دورة اكتشاف التدفق والتغيير ، ومن المحتمل أن يكون الخطأ خطأ؟

من فضلك ، سيكون من الرائع الحصول على نوع من الإجابة الرسمية هنا ...

النظر إلى هذا مرة أخرى. يبدو أن المثال الخاص بي من https://github.com/angular/angular/issues/17572#issuecomment -315096085
كان من المفترض أن يستخدم AfterContentInit بدلاً من AfterViewInit عند محاولة معالجة ContentChildren كجزء من دورة حياة المكون.

إليك plnkr المحدث مع AfterContentInit ولا مزيد من الخطأ: http://plnkr.co/edit/rkjTmqclHmqmpuwlD0Y9؟p=preview

JSMike لقد غيرت AfterViewInit إلى AfterContentInit ، ولا يزال الخطأ هو نفسه. حتى إذا قمت بتحديث بياناتي داخل OnInit يحدث الخطأ. بقدر ما أعلم أن هذا وفقًا لدورة حياة Angular الصحيحة (أليس كذلك؟) لتهيئة البيانات خلال OnInit . إلى جانب حقيقة أن هذا الخطأ لم يحدث في الإصدارات السابقة ، أفترض أن هذا خطأ. ومع ذلك ، أود الحصول على نوع من البيان من فريق التطوير إذا كان الأمر كذلك ..؟ :د

Wernerson ، يجب ألا تقوم بتحديث البيانات داخل ngOnInit ما لم تكن البيانات متاحة في خطاف دورة الحياة هذا. يجب عليك نقل المنطق الخاص بك ليكون في ngAfterContentInit إذا كان يتعلق بأطفال المحتوى.

JSMike لم يساعد ، لا يزال الخطأ يحدث. لقد اشتركت للتو في تغيير الحالة في التطبيق بعد أن تلقيت البيانات المطلوبة (بشكل غير متزامن) في ngAfterContentInit .

Wernerson هل لديك مثال plnkr؟

العينة الأساسية مع StackBlitz
مكون فرعي يصدر حدثًا ويقوم المكون الرئيسي بتحديث متغير محلي.
الحصول على خطأ في وحدة تحكم js:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'hello world'.

تم اكتشاف أن ngAfterContentInit يعمل فقط إذا كان الأطفال ثابتون ، ولا يزال يفشل عند إنشاء الأطفال باستخدام *ngFor http://plnkr.co/edit/rkjTmqclHmqmpuwlD0Y9؟p=preview

soldiertt ، ما عليك سوى اكتشاف تغييرات () https://stackblitz.com/edit/angular-qfu74y

soldiertt التي تبدو غريبة ، لكنها تبدو مشكلة منفصلة يجب الإبلاغ عنها.

JSMike يعد إعدادي معقدًا إلى حد ما مع الخدمات ومكتبات الجهات الخارجية والمعلومات السرية وما إلى ذلك. ولسوء الحظ ، لم أتمكن من إعادة إنتاج الخطأ في مكبسي: /

حقنة من ChangeDetectorRef عملت معي!

لدي هذه المشكلة في المكتبة ، لأنني بحاجة إلى تعيين بعض الخصائص على مكون عنصر المكتبة ، وهي الخاصية التي لا تُعرف إلا بعد حسابها في مكون المكتبة الأصلي. وتأتي جميع العناصر من خلال ng-content ، لذا لا يمكنني العمل إلا مع العناصر الموجودة في ngAfterContentInit لمكون دائري الأصل. شيء مثل:
دائري:

@ContentChildren(ItemComponent) carouselItems;

ngAfterContentInit() {
  // ... some computations
  this.carouselItems.forEach((item: ItemComponent, currentIndex) => {
    item.setPropertyX(someComputedValue);
    // OR:
    item.x = someComputedValue; // of course, this would work as well
  });
}

بند:

setPropertyX(value) {
  this.x = value;
}

بالإضافة إلى الاختراق setTimeout() ، هل سيكون الحل الآخر الوحيد هو استخدام خدمة وموضوع لجعل العناصر تتواصل مع المكتبة الأم؟ أعني أن هذا سيكون سخيفًا ... هذا يعني أنه إذا كان لدي 100 عنصر في دائرة ، فسيكون لدي 100 اشتراك. وهذا يتضاعف إذا كان لدي دائرتان. بالتأكيد لا يمكن أن تكون الطريقة الموصى بها ؟! حق؟

في أغلب الأحيان ، نحتاج إلى تعيين بعض الخصائص على مكوناتنا ، خصائص لا يمكن معرفتها قبل AfterContentInit ... وفي كثير من الحالات (عندما لا نتحدث عن مكون أو مكونين مثل ذلك ، ولكن بالأحرى 50 أو 100 كما هو الحال في الرف الدائري) سيكون الاشتراك من أعشار أو مئات أمرًا سخيفًا.

هل هناك جزء رائع حقًا من Angular يمكن أن يساعدني هنا ، ولم أكتشفه بعد؟

ملاحظة : لقد حاولت أيضًا حقن ChangeDetectorRef واستخدام detach() على كل من مكون الرف الدائري ومكون العنصر. كنت أتوقع أن يصلح هذا الخطأ منذ ذلك الحين ، حسنًا ... أنا أقول بشكل أساسي "_اكتشاف تغيير المسمار ، لا أحتاجه بالفعل على الإطلاق. من فضلك ، أرجو ألا تكتشف أي تغييرات _" ... ولكن لا يزال يتم طرح الخطأ! إذن ، ما الفائدة من طريقة detach() ، إذا لم تفعل ما تقول؟

constructor(private cdr: ChangeDetectorRef) {
  this.cdr.detach();
}

setTimeout() ليس ضروريًا اختراقًا ، فقد يكون طريقة صالحة جدًا للقيام بأشياء مثل ، على سبيل المثال ، عندما تتفاعل مع العرض لتحديثه ، على سبيل المثال: تغيير ارتباطات القالب أثناء ngAfterViewInit .

تكمن المشكلة في استخدامه كسحر دون معرفة السبب حقًا. ولكن هناك سيناريو صالح تمامًا حيث يكون استخدام setTimeout() هو الطريقة الصحيحة.

ghetolay أليس من يتهرب منه نظام الكشف عن التغيير؟ كنت أذهب إلى مسار ChangeDetectorRef.detectChanges () ، ولكن هذا يبدو لي وكأنه اختراق بالنسبة لي ... إذا كان أي من هذه الحلول "مقبولًا" أفترض أنه سيتعين علينا التعايش معه ، ولكنه في الحقيقة يقطع استخدام ViewChild (ren) أو تعديل ContentChild (ren)

تضمين التغريدة

أليس من الاختراقات الخروج من دورة حياة المكون (المكونات) التي يتهرب منها نظام الكشف عن التغيير؟

لا أفهم هذا السؤال. الخروج من دورة الحياة؟

ستقوم Angular بتشغيل جولة من القرص المضغوط في كل مرة تقع فيها أحداث غير متزامنة (حدث dom ، xhr ، setTimeout ، إلخ ..) ، أثناء ذلك القرص المضغوط سيتم تحديث العرض. يتم تشغيل الخطاف ngAfterViewInit بعد ذلك لذلك لا يوجد المزيد من القرص المضغوط المخطط للتشغيل ولن يتم تشغيله مرة أخرى حتى يقع حدث آخر غير متزامن. يعد استخدام setTimeout() أبسط طريقة لتشغيله مرة أخرى. يمكنك بالتأكيد القيام بذلك بنفسك ولكني أعتقد أن الفوائد لا تذكر.

ghetolay أنا أشير إلى التعليق من JSMike وبعض المواضيع الأخرى التي قرأتها:

matkokvesic هذا ليس حلاً حقًا ، إنه يتسبب فقط في حدوث التغيير خارج خطاف دورة الحياة. لا يتسبب مثال الرمز المقدم في حدوث خطأ عند استخدام Angular v4.1.3

لذا أعتقد أن استخدام setTimeout () سيؤدي إلى "تخطي" ربط دورة الحياة أو عدم تقييمه كما هو معتاد؟

ghetolayfugwenna ماذا عن detach() طريقة من ChangeDetectorRef ؟ أي أفكار؟ ألا يجب أن يؤدي ذلك إلى تعطيل اكتشاف التغيير تمامًا ، وبالتالي التخلص من الخطأ أيضًا؟ أنا لا أفهم لماذا لا يعمل؟ أم أن الخطأ القيت "مستقل" عن ذلك؟ بمعنى أن Angular لا يهتم حقًا بحقيقة أننا قمنا بتعطيل اكتشاف التغيير ، فهو يهتم فقط بتغيير خاصية ما بعد تعيينها؟
إذا كان الناس لا يزالون يتجادلون حول "ExpressionChangedAfterItHasBeenChecked" كونه خطأ أم لا ، فربما يكون هذا خطأ بالتأكيد ... في رأيي ، لا ينبغي أن تهتم Angular لنا بتغيير الخصائص بعد تعيينها ، إذا كنا كشف التغيير المعطل. خاصة بالطريقة التي قمت بها ، للمكون الرئيسي وجميع المكونات الفرعية الأخرى ، على شجرة بأكملها بشكل أساسي.

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

على سبيل المثال ، قمت بكتابة مكون موصل ذي قيمة تحكم يغلف منتقي البيانات. أقوم بتغذية هذا المكون بنموذج فارغ يتم تمريره داخليًا إلى منتقي البيانات والذي يقوم بتهيئته وإرساله مرة أخرى من خلال الوظيفة المسجلة. بالطبع يقوم الأطفال بتعديل النموذج أثناء بدايته ، لكن هذا ما أريده! أرغب في استعادة نموذج مهيأ ولكن لا أقوم بالتهيئة في الأصل لأن منتقي البيانات هو الذي يتعهد بكيفية القيام بذلك.

القضية هي كيف أفعل ذلك الآن؟ استدعاء setTimeout وتشغيل الكشف عن التغيير مرة أخرى؟ استخدام ChangeDetectorRef؟ لا أحبه لأنه يجعل قراءة الكود صعبة حقًا. تهيئة الشيء في خدمة منفصلة؟ لكن هذه الخدمة تخدم فقط غرضًا تقنيًا للغاية دون أي قيمة تجارية ولست متأكدًا من أن هذا سيحل المشكلة.

fugwenna كان شاغلي الرئيسي هو أنه يعمل في 4.1.3 وقد تم كسره الآن. لم أر أي شخص في الفريق الزاوي يتناغم حتى الآن إذا كان علينا استخدام setTimeout في وظائف init الخاصة بنا والسماح للمناطق بالسيطرة ، لكنه يبدو وكأنه نمط مضاد مقارنة بكيفية عمله سابقًا.

@ kavi87 لذلك هذا ستاكوفيرفلوو السؤال فعلا أعطيا لي حل أنيق احتاجه. وهو ما جعل الحدث ينبعث من المكون الفرعي الخاص بي غير
new EventEmitter(true)

بالنسبة لقضية تم تصنيفها على أنها تراجع مع 109 تعليقًا ، فإن هذه المشكلة هادئة بشكل مخيب للآمال مع أي كلمة رسمية.

بينما يقوم كل شخص هنا بعمل جيد مع تقنيات المساعدة الذاتية ، أود أن أعرف ما إذا كان هذا في الواقع خطأ ، أم مجرد سلوك جديد تم تنفيذه في 4.2.x وما بعده؟ إذا كان الأمر كذلك ، فيجب أن يتم إدراجه كتغيير كسر (وبالتالي ليس إصدارًا بسيطًا؟)

تضمين التغريدة
لست متأكدًا من أن تعطيل اكتشاف التغيير تمامًا لإسكات استثناء يعد طريقًا جيدًا للتراجع ، على الرغم من أنني من الناحية النظرية ، أرى كيف يجب / يمكن أن يكون منطقيًا.

أتفق مع JSMike و rosslavery فيما يتعلق بالقليل جدًا من الاستجابة أو التفسير لسبب ظهور رسالة الخطأ هذه حول المنشور 4.2x دون أي توثيق لتغييرات التكسير ... ولكن هناك جزء محير آخر من هذا هو كيف يبدو أنه تم إلقاؤه فقط في وضع التطوير ، والذي يبدو تقريبًا وكأنه تحذير بمقاطعة اكتشاف التغيير (؟)

أنا AppComponent لديّ div.
الغرض الوحيد من هذا div هو إظهار تراكب مشغول.
يتم تشغيل حالة الانشغال داخل فئة خدمة http:

app.component.html

<!-- Busy Overlay -->
<div *ngIf="busy$ | async"
     class="overlay">
    <i class="loading">
        <i><sk-wave></sk-wave></i>
    </i>
</div>

app.component.ts:

  get busy$(): Observable<boolean> {
    return this.catolService.busy$;
  }

بعد الترقية إلى 4.3.4 ، أحصل دائمًا على خطأ ExpressionChangedAfterItHasBeenCheckedError عندما تقوم الخدمة بتشغيل حالة الانشغال.

هل يوجد حل لهذا السيناريو غير المتزامن البسيط؟

@ emazv72 ... هل هذا العرض التوضيحي stackblitz / plunker هو نفسه مثل قضيتك؟ https://stackblitz.com/edit/angular-2p2h9s؟file=app٪2Fapp.component.ts أو https://plnkr.co/edit/RPOxSVnXvM1TqKp0peSY؟p=preview

mukunku شكرا ، سأحاول ذلك.

@ mlc-mlapis لقد غيرت رمز plunker الخاص بك قليلاً هو https://plnkr.co/edit/uUT7tiM5BKPIy2upJPhb؟p=preview

لكن الغطاس لا يُظهر المشكلة. سأحاول الترقية إلى الإصدار 4.x الأحدث لمعرفة ما إذا كان قد تم إصلاحه.

شكر

مرحباً بالجميع - أعتذر عن التأخير هنا.

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

يعد plunkersaveretts نسخة جيدة من هذا للمشي من خلاله (شكرًا!) - https://plnkr.co/edit/fr7rTNTCgEhHcbKnxAWN؟p=preview

TLDR هو أن Angular يعمل دائمًا بهذه الطريقة - اكتشاف التغيير قيد التشغيل ، من أعلى إلى أسفل (أو الأصل -> الطفل) ، في مسار واحد. لذلك ، إذا كان لدينا:


@Component({ 
  selector: 'app-root',
  template: `
    <!-- initially falsey -->
    <div *ngIf="sharedService.someValue">NgIf Block</div>
    <child-comp></child-comp>
  `
})
class AppRoot {
  constructor(public sharedService:SharedService){}
}
@Component({ 
  selector: 'child-component',
  template: `child comp`
})
class ChildComponent {
  constructor(public sharedService:SharedService){}
  ngOnInit(){
    this.sharedService.someValue = true;
  }
}

سيتم تشغيل اكتشاف التغيير لجذر التطبيق أولاً ، والتحقق من جميع ارتباطاته ، ثم تغيير اكتشاف المكون الفرعي - مرة أخرى ، لأننا نقوم بذلك في مسار واحد (بدلاً من الاستمرار في التحقق من نمط AngularJS حتى الاستقرار) ، (* ngIf ، في هذه الحالة) لم تتم إعادة فحصه - لذا أصبح التطبيق الآن في حالة غير متسقة (!)

في وضع التطوير ، نقوم بالفعل بتشغيل اكتشاف التغيير هذا مرتين - تتحقق المرة الثانية التي يتم تشغيلها من أن الروابط لم تتغير (مما قد يشير إلى حالة غير متسقة) ، وتلقي بالخطأ إذا تم العثور على مثل هذه المشكلة. يمكنك التحقق من هذا السلوك عن طريق استدعاء enableProdMode() في Repro: https://plnkr.co/edit/GGEzdxK1eHzHyGiAdp56؟p=preview. لاحظ أنه لم يتم طرح الخطأ (لأننا لا نقوم بتشغيل القرص المضغوط x2) ، ولكن ينتهي الأمر بالتطبيق في حالة غير متسقة (يظهر الطفل ، ولكن ليس * ngIf) ، وهو أمر سيئ.

سبب ظهور ذلك مؤخرًا هو أنه في السابق ، قام جهاز التوجيه بإجراء عدد من عمليات التحقق الإضافية للكشف عن التغيير عند إدخال المكونات - كان هذا غير فعال وتسبب في حدوث مشكلات في الرسوم المتحركة والتوجيه ، خاصةً عندما تكون منافذ جهاز التوجيه متداخلة في ngIfs وما شابه.

أود أن أزعم أن الكود الذي ينتهك هذا التدفق أحادي الاتجاه (كما يفعل Repro أعلاه) غير صحيح . ومع ذلك - هناك حالات قد ترغب فيها في هذا السلوك (في الواقع ، لدينا إعداد مماثل في مكتبة نماذج Angular). ما يعنيه هذا هو أنك تحتاج إلى تشغيل تشغيل آخر لاكتشاف التغيير بشكل صريح (والذي سيعود إلى مكون الجذر والتحقق من أعلى لأسفل). يؤدي استدعاء setTimeout ذلك ، على الرغم من أنه من الأفضل عادةً استخدام وعد لتشغيل هذا - راجع https://plnkr.co/edit/IoKMxEOlY9lY9LcWwFKv؟p=preview

امل ان يساعد!

robwormald كان نفس الخطأ يحدث مع سيناريوهات أخرى أيضًا. لست متأكدًا مما إذا كان هذا يعالج المشكلة مع ContentChildren . لماذا يمكن أن يغير التوجيه قيم الأطفال أثناء AfterContentInit بدون أخطاء ولكن فعل الشيء نفسه في أحد المكونات يؤدي إلى حدوث أخطاء؟

أيضًا ، هل من المتوقع أنه إذا تم استدعاء cdr.detectChanges() أثناء عملية بدء واحدة ، فلن يتم استدعاء وظائف init الأخرى؟ هذا شيء تم اكتشافه في هذا الموضوع الذي أنشأت مشكلة منفصلة له .

تضمين التغريدة

أعتقد أن JSMike صحيح في التقييم بأن هذا الخطأ لا يزال يتم طرحه أثناء السيناريوهات التي يأتي فيها التدفق أحادي الاتجاه من أحد الوالدين-> طفل يستخدم ContentChild (ren) و ViewChild (ren).

هل من المقبول اعتبار ما قلته من قبل ، هذا الخطأ أكثر من "تحذير" ينص على أن المطور يتعارض مع "أفضل الممارسات" للتدفق أحادي الاتجاه ولكنه لا يتسبب في أي أخطاء في كسر التطبيق ، منذ اكتشاف التغيير يعمل مرة واحدة فقط أثناء وضع الإنتاج (العالم الحقيقي)؟

fugwenna لا ، هذا خطأ حقًا ، لأنه بخلاف ذلك يترك العرض في حالة غير متسقة. العينة التي يمر بها روب تخبرنا بكل شيء.

أيضا ، هناك هذا الاقتباس من المستندات

تمنع قاعدة تدفق البيانات أحادية الاتجاه في Angular التحديثات على طريقة العرض بعد تكوينها. يتم إطلاق كلا الخطافين بعد تكوين عرض المكون.

إذا كنت حقًا بحاجة إلى تغيير أي شيء هناك ، فاستخدم إحدى الطرق غير المتزامنة للقيام بذلك. قد يكون setTimeout مناسبًا هنا ، ولكن اعلم أن ذلك يأتي مع تأخير (في وقت كتابة هذا التقرير) 4 مللي ثانية. طريقة أخرى هي Promise.resolve().then(() => Assignment = here)

والأفضل من ذلك هو التراجع خطوة إلى الوراء ، ومعرفة سبب حاجتك إلى إجراء تغيير في وقت متأخر من العملية ومعرفة ما إذا كانت هناك طريقة أنظف لحل المشكلة. إذا وجدت نفسي أفعل هذا ، فأنا أتعامل معه كرائحة كود.

شكرًا لك على الشرح التفصيليrobwormald. ما زلت في حيرة من أمري لماذا لا يزال تدفق البيانات الأصل يعمل عند استخدام [hidden] ملزمة بدلاً من *ngIf (انظر https://plnkr.co/edit/L4QWK5pTgF1qZatFAN4u؟ = معاينة). أي أفكار لماذا لا يزال هذا يعمل دون إعطاء ExpressionChangedAfterItHasBeenCheckedError ؟

يستخدم Angular وظيفة checkAndUpdateView لإجراء اكتشاف التغيير.

قد يبدو التمثيل القصير لهذه الوظيفة كما يلي:

function checkAndUpdateView(view) {
    // update bound properties and run ngOnChanges/ngOnInit/ngDoCheck for child directives
    updateDirectives
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
    // update DOM for the current view
    updateRenderer
    // perform checkAndUpdate for all child components
    execComponentViewsAction(view, ViewAction.CheckAndUpdate);
}

يتم تنفيذه بشكل متكرر بفضل طرق execEmbeddedViewsAction و execComponentViewsAction . وأيضًا النقطة الأساسية هنا هي عمليات التحقق الزاويّة المضمنة بعد التحقق من التوجيهات وقبل تحديث DOM.

لتبسيط مثال saverett سأستخدم القالب التالي

<div *ngIf="!sharedService.displayList">Some text</div>
<router-outlet></router-outlet>

https://plnkr.co/edit/OdkzHjEXbEd3efYAisFM؟p=preview

لقد عرفنا بالفعل أن *ngIf هو مجرد ديسوغار. وبالتالي

<ng-template [ngIf]="sharedService.displayList">
  <div>Some text</div>
</ng-template>
<router-outlet></router-outlet>

يمكننا تخيل الهيكل التالي:

  my-app
     |
   NgIf directive
       NgIf EmbeddedView
   RouterOutlet directive

ماذا يفعل التوجيه RouterOutlet ؟

يقوم بحقن المكون الموجه في ViewContainerRef

activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
    ...
    this.activated = this.location.createComponent(factory, this.location.length, injector);

هذا يعني أن router-outlet يُنشئ EmbeddedView لذلك بعد تهيئة التوجيه RouterOutlet لدينا عرضان مضمّنان في عرض AppComponent .

  my-app
     |
   NgIf directive ([ngIf]="sharedService.displayList")
       NgIf EmbeddedView
   RouterOutlet directive 
      BComponent EmbeddedView

دعنا نوضح ذلك في الصورة

haschanged

لنعد الآن إلى وظيفتنا الرئيسية

function checkAndUpdateView(view) {
    // update bound properties and run ngOnChanges/ngOnInit/ngDoCheck for child directives
    updateDirectives
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
    ...
    // update DOM for the current view
    updateRenderer
    ...
}

سيقوم Angular بإجراء الفحص بالترتيب التالي:

NgIf directive ([ngIf]="sharedService.displayList") updateDirectives(1)
      NgIf EmbeddedView   execEmbeddedViewsAction(2)
RouterOutlet directive 
      BComponent EmbeddedView  execEmbeddedViewsAction(3)

سيتم التحقق من توجيهنا NgIf أولاً (هنا يتم تذكر sharedService.displayList=false ) ثم سيتم استدعاء طريقة NgOnInit داخل BComponent (حيث نقوم بتغيير sharedService.displayList إلى true ) وعندما تؤدي الزاوية checkNoChanges سترفع الخطأ Expression has changed after it was checked

ثم لنستبدل *ngIf بـ [hidden] . الآن سيكون لدينا عرض مضمّن واحد فقط مُدرج بواسطة RouterOutlet [hidden]="sharedService.displayList" وسيتم فحص updateRenderer

  my-app
     |
   div{Some Text} just a node
   RouterOutlet directive 
      BComponent EmbeddedView

سيقوم Angular بإجراء فحص مثل هذا:

function checkAndUpdateView(view) {
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);   BComponent.ngOnInit (1)
    ...
    // update DOM for the current view
    updateRenderer   update hidden binding (2)
    ...
}

بهذه الطريقة لن يتسبب ربط خاصية dom في الخطأ لأننا قمنا بالتحديث بعد استدعاء ngOnInit .

هذا هو الاختلاف الرئيسي بين التوجيه الهيكلي *ngIf وربط خاصية dom [hidden]

شكرا لك على النظرة العامة المفصلةalexzuza! كانت مفيدة جدا!

setTimeout () عملت بالفعل بالنسبة لي :) شكرًا jingglang :)
.subscribe (data => setTimeout (() => this.requesting = data، 0))

robwormald صحيح هنا. لقد واجهت نفس المشكلة وكان اكتشافها أكثر صعوبة لأنني كنت أستخدم NGRX والملاحظات. كانت المشكلة التي واجهتني هي وجود حاويات مكونة على مستويات مختلفة في التسلسل الهرمي للطريق والتي كانت تشترك في حالة المتجر. بمجرد أن نقلت مكونات الحاوية الخاصة بي إلى نفس المستوى في وحدة الميزات الخاصة بي ، اختفت المشكلة. أعلم أن هذا نوع من الإجابة المعقدة ، ولكن إذا كنت تستخدم نموذج حاوية / مكون NGRX ، نأمل أن يؤدي هذا إلى توجيه تحقيقك في الاتجاه الصحيح

مع setTimeOut تعمل ، شكرًاjingglang
ngAfterViewInit () {
this.innerHeight = (window.innerHeight) ؛
setTimeout (() => this.printPosition ()، 0) ؛
}

alexzuza ، هذا لا يحدث فقط لـ ngIf ، هذا يحدث (على الأقل بالنسبة لي) أيضًا مع [ngClass]

@ Ninja-Coding-Git لقد وصفت للتو حالة معينة. أعلم أنه يمكن أن يحدث مع أي رمز.

بالنسبة لي ، أدى استدعاء ChangeDetectorRef بعد تغيير العنصر إلى حل المشكلة:

import { Component, ChangeDetectorRef } from '@angular/core'

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message: string = 'loading :(';

  constructor(private _cdr: ChangeDetectorRef) {}

  ngAfterViewInit() {
    this.message = 'all done loading :)'
    this._cdr.detectChanges();
  }

}

تم العثور على الحل هنا

مرحباthetylerb! هل يمكنك توضيح المزيد حول هذا الموضوع. نحن نستخدم ngrx ولا أعرف كيفية تعويض ذلك. أعتقد أن استخدام الوعود فوق ما يمكن ملاحظته أمر محرج للغاية.

@ piq9117 يمكنك أيضًا إلقاء نظرة على هذا التعليق https://github.com/angular/angular/issues/18129#issuecomment -326801192 التي قمت بها على التفاعل بين اكتشاف التغيير و ngrx ، يمكن أن يكون مفيدًا على ما أعتقد

@ victornoel شكرا! هذا يصف حالتنا إلى حد كبير. لدينا هياكل بيانات متداخلة غير قابلة للتغيير (immutablejs) ، ونستخدم أنابيب غير متزامنة في كل مكان. كانت أفكاري الأولية أن المتجر سيغير شجرة المكونات عندما يتم ترطيب المتجر ، وستقوم شجرة المكونات بالتعويض عن هذا التغيير وإجراء تغيير آخر الكشف 😓

لدينا هياكل بيانات متداخلة غير قابلة للتغيير (immutablejs) ، ونستخدم أنابيب غير متزامنة في كل مكان. كانت أفكاري الأولية أن المتجر سيغير شجرة المكونات عندما يتم ترطيب المتجر ، وستقوم شجرة المكونات بالتعويض عن هذا التغيير وإجراء اكتشاف تغيير آخر.

في مشروعي ، لدينا نوع من بنية البيانات الطبيعية (بدلاً من هياكل البيانات المتداخلة) ، لذا فهي تخفف قليلاً من هذا النوع من المشاكل ، حتى لا يحدث ذلك! : يا

تأمل فقط إما:

  • تغيير هياكل البيانات المخزنة لديك بحيث لا تؤدي التغييرات على جزء واحد إلى تشغيل تحديثات لأجزاء أخرى
  • الحرص على عدم تشغيل الأحداث من العناصر الفرعية التي تستمع إلى المتجر عبر أنبوب غير متزامن
  • تشغيل اكتشاف التغيير يدويًا عند تشغيل الأحداث المذكورة أعلاه
  • تشغيل تلك الأحداث في وقت آخر أو في setTimeout

أنا أستخدم ngrx وحصلت على موقف مثل هذا:

في app.component.html:
<div *ngIf='(appState | async).show'>test</div>
وفي app.component.ts
this.appState= this.store.select("app");

بالطبع في هذه الحالة ، أحصل على الخطأ ، لكن عندما أقوم بإضافة علامة منطقية في مكون مثل هذا:
app.component.html -> <div *ngIf='show'>test</div>
app.component.ts ->
this.appState.subscribe((state: AppState) => { this.show= state.show; })

لقد ذهب الخطأ طبعا من المفهوم بالنسبة لي لماذا ولكن أريد أن أعرف آرائكم هل هو حل جيد؟ ما هي الايجابيات والسلبيات ؟؟

لا أستخدم ngrx ولكني أواجه مشكلة في إجراء فحص بسيط باستخدام querySelectorAll let chips = [].map.call(document.querySelectorAll('li.chip'), this.chipVisible);

يعمل حل ChangeDetectorRef.detectChanges() منع الخطأ ولكن سيكون من الجيد ألا تضطر إلى استيراد ChangeDetectorRef عن طريق إصلاح أي شيء يسبب الخطأ في المقام الأول.

حصلت على نفس المشكلة عند استخدام rxjs في الزاوي 4.4.4. لقد اشتركت للتو في كائن يمكن ملاحظته توفره خدمة DI. عملي في الجوار أدناه:

this.eventBroadcastService.getCustomEvent({type: 'loginOpen'}).subscribe(e => { setTimeout(() => { this.isBlur = true; }, 0); });

أنا استخدم setTimeout للعمل. إنه يعمل الآن ، لكنه صعب. لا أشعر بالرضا.

يقوم ngOnChanges (التغييرات: SimpleChanges | أي) بالمهمة

تم إصلاح المشكلة ببساطة بعد نقل منطق معلمات التغيير الديناميكي داخل طريقة ngDoCheck ().
ngDoCheck(): void { this.checkValid = this.sourceArr.includes((this.chanedVal).toUpperCase()); }

@ asrinivas61 أعتقد أن هذه فكرة سيئة حقًا. يتم استدعاء ngDoCheck كثيرًا بواسطة Angular وإذا كان sourceArr يحتوي على الكثير من البيانات ، فقد يضر ذلك بأدائك.

راجع https://angular.io/guide/lifecycle-hooks#other -angular-lifecycle-hooks

أفضل أن أوصي الآن باستخدام ngAfterViewInit مع setTimeout :

setTimeout(_ => /* your code here */, 1000);

مثال:

  public ngAfterViewInit(): void {
    // We use setTimeout to avoid the `ExpressionChangedAfterItHasBeenCheckedError`
    // See: https://github.com/angular/angular/issues/6005
    setTimeout(_ => this.isLoadingInProgress = false, 1000);
  }

dgroh ، لست بحاجة إلى إضافة مهلة فعلية ، فأنت تريد فقط أن يقوم js بتنفيذ التعليمات البرمجية الخاصة بك في مهمة غير متزامنة تالية مما يعني تشغيل اكتشاف تغيير آخر.

victornoel أعتقد بارلي أن هذا سيعمل مع detectChanges من ChangeDetectorRef . أتمنى ألا يكون كل هذا مطلوبًا.

dgroh لا بد أنك أساءت فهمي ، كنت

public ngAfterViewInit(): void {
    // We use setTimeout to avoid the `ExpressionChangedAfterItHasBeenCheckedError`
    // See: https://github.com/angular/angular/issues/6005
    setTimeout(_ => this.isLoadingInProgress = false);
  }

أليس من الممكن إضافة خطوة بعد ngAfterViewInit مثل setTimeout ؟ (على سبيل المثال: ngAfterViewLoad )

@ aks4it ، يبدو أن هذا يعمل ، لكني أحب المزيد من التفاصيل حول كيفية عمله؟ هل رمز توليد المكون الديناميكي متزامن؟

يحتوي العرض التوضيحي الرسمي لبرنامج Plunker من مستندات الزاوية على نفس الخطأ. هل يمكنك إصلاحه هناك؟ :) شكر

تضمين التغريدة

  ngAfterViewInit() {
    //this.loadComponent();
    this.getAds();
  }

هذا يعمل؟

أنا لا أفهم متطلباتك لكنك تريد حقًا فاصلًا من 3 ثوانٍ؟ إذا كانت الإجابة بنعم ، فهذا يعمل ، وحتى إذا كنت تريد الاتصال مباشرة بـ loadComponent ، فأنا أحتاج إلى:

ngAfterViewInit() {
    setTimeout(()=>{this.loadComponent()},0)
  }

istiti تمكنت من إصلاحه عن طريق الحل البديل ChangeDetectorRef ، ووضع detectChanges() بعد ... data = data

ولكن نظرًا لأنه من المستندات الرسمية ، كنت أشعر بالفضول بشأن الحل الرسمي للمشكلة. ليس مهلة أو ChangeDetectorRef.

آسف ، لقد كنت موجزًا ​​جدًا في المنشور الأول .. أردت أن أشير إلى خطورة المشكلة ، حتى المستندات الزاويّة لها نفس الرمز

فقط لمعلوماتك ، لست متأكدًا مما يسبب هنا حقًا ، لكن الأخطاء قد ولت عندما قمت بتمكين وضع prod في main.ts
`enableProdMode ()

// if (environment.production) {

// enableProdMode () ؛

//} `

"@ angular / core": "^ 4.2.4"
"مطبوعة": "~ 2.3.3"

bakthaa ... إنه سلوك مفترض لأن فحص التحكم (ما إذا كانت قيم المكونات @Inputs() هي نفسها) والذي يتم استدعاؤه بعد استدعاء كل دورة CD فقط في وضع التطوير وليس في وضع الإنتاج لتطبيق Angular. لذا فإن المشكلة مخفية في وضع الإنتاج ولكنها لا تزال موجودة ... وربما يمكن أن تكون مصدرًا لآثار جانبية مختلفة غير مرغوب فيها أو مضللة في وضع الإنتاج.

أنا أستخدم Angular 5 ، وكنت قادرًا على إصلاح هذه المشكلة عن طريق إدخال ChangeDetectorRef في المكون وإجباره على اكتشاف التغييرات.

    constructor(private changeDetector: ChangeDetectorRef) {}

    ngAfterViewChecked(){
      this.changeDetector.detectChanges();
    }

@ Sabri-Bouchlema ... نعم ، إنها طريقة واحدة ... ولكن لا يزال عليك إعادة التفكير مرة أخرى ... لماذا تم تغيير أحد المكونات @Inputs خلال دورة قرص مضغوط واحد ... وتقرر ما إذا كان المنطق صحيح ... وصحيح أنه من 95٪ من الحالات يمكن تغيير الكود لتجنب المشكلة دون استدعاء detectChanges() إضافي.

باستخدام الزاوية 5.1.2. أتلقى هذا الخطأ عند فتح مربع حوار المواد في ngAfterViewChecked. كما ذكرت @ aks4it ، اضطررت إلى لف الكود في setTimeout لجعله يعمل.

أواجه نفس المشكلة مع الإصدار 4.01
إصدار ng = "4.0.1

نفس الشيء هنا مع الزاوي 4.4.6

إصلاح باستخدام this.changeDetector.detectChanges();

لقد كتبت هذا plunker باستخدام متغيرات القالب ولا أفهم سبب وجود ExpressionChangedAfterItHasBeenCheckedError. إذا قمت بتغيير الترتيب في مكوناتي ، يختفي الخطأ:

https://plnkr.co/edit/Mc0UkTR2loI7wgmLAWtX

لقد حاولت changeDetector.detectChanges ولكن لا يعمل

AlexCenteno ... لأن منطق التقييم من أعلى إلى أسفل ... و value خاصية comp1 تم تعيين @Input() public value:String=null; أولاً ثم إعادة تعيينها باستخدام مرجع comp2.value ... <div comp1 [value]="comp2.value"></div> الذي تم ضبطه في هذه الأثناء على Hello خلال @Input() ... كل ذلك مع دورة قرص مضغوط واحدة ... لذلك الفحص المتكرر إذا كانت المدخلات على المكونات الفرعية هي نفسها كما في بداية دورة القرص المضغوط في وضع dev ، يكتشف التغيير في الخاصية value على comp1 .

مرحبا @ mlc-mlapis شكرا لك على وقتك. إذن لا توجد طريقة لتجنب الخطأ؟ أعتقد أنه شيء لا يجب أن أقلق بشأنه بمجرد الاتصال بـ enableProdMode؟

AlexCenteno ... نعم ، في وضع prod لا يتم استدعاء الفحص بعد دورة القرص المضغوط ... لكن هذا لا يعني أنك يجب أن تكون راضيًا عن ذلك. يجب عليك تطبيق منطق مختلف لتمرير value عبر المكونات الفرعية. هناك أيضًا مشكلة تتمثل في أن <div> ليس له خاصية value . لماذا تستخدم مكون السمات على الإطلاق؟ لماذا ليس فقط:

<comp1 [value]="comp2.value"></comp1>
<comp2 #comp2 value="Hello"></comp2>

لأن استخدام @Output() على <comp2> سيكون أمرًا جيدًا وسيتيح لك تحديث [value] على comp1 دون أي خطأ.

استخدام [email protected] مع angular@5 standart store.select طرح تحذير ExpressionChangedAfterItHasBeenCheckedError في وضع dev والعمل بشكل غير صحيح في وضع prod. الإرسال والاختيار على مستويات / حاويات مختلفة.

@Component({
  ...
  template `
  ...
      <div [ngClass]="{
        'home__sidenav': true,
        'home__sidenav--active': (isActiveSidenavMenu$ | async)
      }">
  ...
  `
})
export class HomeContainer {
  isActiveSidenavMenu$ = this.store.select(fromHome.isActiveSidenavMenu);
  ...
}

تزيل هذه الطريقة رسالة التحذير ، لكنها تبدو أكثر تعقيدًا:

@Component({
  ...
  template `
  ...
      <div [ngClass]="{
        'home__sidenav': true,
        'home__sidenav--active': isActiveSidenavMenu
      }">
  ...
  `
})
export class HomeContainer {
  isActiveSidenavMenu$ = this.store.select(fromHome.isActiveSidenavMenu);
  isActiveSidenavMenu;

  ngOnInit() {
    this.isActiveSidenavMenu$.subscribe(res => {
      this.isActiveSidenavMenu = res;
      this.cd.detectChanges();
    });
  }
}

هل هناك فكرة عن كيفية حل هذه المشكلة بشكل صحيح؟

rimlin هل جربت الاشتراك مباشرة في المتجر؟

ngOnInit() {
  this.store.select(fromHome.isActiveSidenavMenu)
    .subsbrice(res => {
      this.isActiveSidenavMenu = res;
    });
}

أعتقد أن لدي مشكلة مماثلة. ومن خلال تجنب async في القالب حل المشكلة.

@ piq9117 شكرًا على النصيحة ، جرب الحل الخاص بك ، يبدو أكثر وضوحًا مع رمز أقل ، ولكن هنا تحتاج أيضًا إلى الاتصال بـ this.cd.detectChanges(); ، وإلا فلن يتم تحديث العرض.

ngOnInit() {
    this.store.select(fromHome.isActiveSidenavMenu).subscribe(res => {
      this.isActiveSidenavMenu = res;
      this.cd.detectChanges();
    });
}

أنا أستخدم Angular 5.2 ، لقد فعلت شيئًا كهذا. يعمل لدي

constructor(private changeDetector: ChangeDetectorRef) {}

ngAfterViewInit(){
  this.changeDetector.detectChanges();
}

باستخدام Angular 5.2 ، ثابت باستخدام DiscoverChanges ()

نعم ، لقد أصلحنا ذلك باستخدام مسار () DiscoverChanges أيضًا (Angular 5.1). أنا مع العديد من الآخرين هنا على الرغم من أن هذا يعتقد أن هذا لا يزال مجرد حل بديل ولا ينبغي معاملته كإجابة على المدى الطويل. يجب أن يكون هناك خطاف آخر لدورة الحياة حتى تتمكن من استخدامه.

إن بدء دورة جديدة تمامًا للكشف عن التغيير للسماح بإجراء تغييرات كهذه يبدو دون المستوى الأمثل.

يبدو أن هذا مستمر ومستمر على الرغم من أن جميع الإجابات والنصائح قد تم نشرها بالفعل أعلاه.

  1. الطريقة الصحيحة لإصلاح ذلك هي إعادة تصميم / إعادة هيكلة المكونات ومعالجة البيانات الخاصة بك للعمل مع مرور واحد للكشف عن التغيير من الأعلى إلى الأسفل. هذا يعني أنه لا يجوز لأي مكون فرعي تغيير قيمة مستخدمة في قالب المكون الرئيسي. أعد تقييم ما قمت بإنشائه وتحقق من أن البيانات تتدفق في اتجاه واحد فقط ، أسفل شجرة المكونات. قد يكون هذا بسيطًا مثل نقل الكود الخاص بك من ngAfterViewInit وإلى ngOnInit في بعض الحالات.
  2. this.changeDetectorRef.detectChanges(); هي الطريقة الرسمية والمعترف بها للتعامل مع هذا الأمر في أقل من 5٪ من الحالات التي تكون فيها الخطوة الأولى مشكلة.

الملاحظات الأخرى:

  1. كن حذرًا جدًا عند القيام بأي شيء في ngAfterViewInit حيث أن تغيير القيم المستخدمة في DOM يمكن أن يسبب هذا الخطأ.
  2. كن حذرًا جدًا عند استخدام أي من خطافات دورة الحياة Check أو Checked حيث قد يتم استدعاؤها في كل دورة اكتشاف تغيير.
  3. يمكن حل هذا الأمر مع setTimeout ، ولكن يفضل الحلان أعلاه.
  4. تجاهل هذا الخطأ في وضع dev والسعادة لأن وضع الإنتاج "يصلح" الخطأ غير صالح كما هو موضح أعلاه. لا يتم إجراء هذه الفحوصات في وضع المنتج ، لكن كود وضع المنتج الخاص بك لا يزال يترك التغييرات غير مكتشفة ويتسبب في عدم تزامن العرض والبيانات. قد يكون هذا غير ضار في بعض الحالات ، ولكن يمكن أن يؤدي إلى تلف البيانات / العرض ومشاكل أسوأ إذا تم تجاهلها.
  5. لم يتغير تدفق البيانات بطريقة واحدة كثيرًا في Angular ، لذا فإن النشر الذي وصلت إليه في الإصدار X من Angular ليس مفيدًا للغاية.
  6. لا تعد إضافة المزيد من أدوات ربط أو حالات دورة الحياة حلاً لذلك. سيضيف المطورون فقط رمزًا ينتهك تدفق البيانات في اتجاه واحد إلى هذه الخطافات ويؤدي إلى نفس الخطأ.

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

تتعمق المنشورات أعلاه والعديد من منشورات المدونة بشكل أعمق في هذا الأمر وتستحق القراءة حيث يجب أن تفهم نموذج تدفق البيانات أحادي الاتجاه وكيفية إنشاء المكونات وتمرير البيانات بداخله.

Splaktar هل من الأفضل قفل هذه المشكلة (وحذفها) لجعل التعليق أعلاه بارزًا؟ أصبح الخيط طويلًا بالفعل وكان معظمهم يتكرر. 😃

Splaktar لقد فرعي تغيير القيمة المستخدمة في قالب المكون الرئيسي".

لنفترض أن لديك مكون حاوية يتضمن التنقل وعناوين الصفحات وما إلى ذلك ، وأنه يقوم بتحميل مجموعة من المكونات الفرعية ، كل منها قد يقوم بتحميل مكوناته الفرعية. ستتغير المكونات التي يتم تحميلها في الحاوية بناءً على البيانات ، وستتغير المكونات الفرعية التي يتم تحميلها بناءً على البيانات. يجب أن يكون كل مكون من هذه المكونات الفرعية قادرًا على التواصل مرة أخرى مع معلومات الحاوية الرئيسية حول ماهيتها حتى تتمكن الحاوية الرئيسية من عرض المستوى الأعلى المناسب للتنقل للمستخدم ربما (ربما مسار التنقل ، أو شيء مشابه) ، أيضًا كمعلومات يمكن استخدامها في العناوين لتحديد ما يتم تحميله للعرض على مستوى الحاوية.

في هذا ، وهو هيكل شائع للغاية في تجربتي ، سأستفيد من خدمة الحالة العامة التي تسجل المكونات الفرعية نفسها بها ، ويشترك المكون الرئيسي فيها حتى يتمكن الأطفال من تعليم الوالد والوالد يمكن أن تعرض المعلومات المناسبة.

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

يبدو أن هذا يتعارض مع ما تقترحه أعلاه ، وبالنسبة لي هو مصدر المشكلة. عندما يقوم المكون الفرعي بالتسجيل مع الخدمة ، يقوم المكون الرئيسي بإلقاء الخطأ.

هل هذا غير صحيح؟

ولا تنس أبدًا أن هناك خطأ معتمدًا:
https://github.com/angular/angular/issues/15634

@ Martin-Wegner يرجى الاطلاع على https://github.com/angular/angular/pull/18352#issuecomment -354170352

alignsoft ... هل لديك نسخة بسيطة من حالتك والمشكلة باعتبارها Stackblitz؟ لأنه يبدو أن شيئًا آخر يجب أن يؤثر على الحالة بخلاف المنطق الذي وصفته.

@ trotyl لا ترتبط الحلول من https://github.com/angular/angular/issues/17572#issuecomment -311589290 أو؟

@ Martin-Wegner ... في حالتك يكفي تغيير الخطاف ngAfterViewInit إلى ngOnInit .

alignsoft ، حيث قال @ mlc-mlapis إن مثال Stackblitz سيكون مفيدًا جدًا في إظهار البنية المناسبة لهذه الحالة. هذا ممكن جدًا باستخدام المجموعة الصحيحة من الخدمات ، والملاحظات ، وخطافات دورة الحياة ، وما إلى ذلك. ستكون هذه الحالة المحددة أيضًا موضوعًا جيدًا لـ StackOverflow.

Splaktar بمجرد أن أحصل على الموعد النهائي الحالي من مكتبي ، سأصمم مشروعًا يوضح حالة بسيطة.

@ mlc-mlapis نجاح باهر شكرا ، يعمل :)

alignsoft ، اليوم كنت أقوم بإنشاء تطبيق يتصرف تمامًا كما وصفته. @ mlc-mlapis ، يمكنك العثور على المثال الذي تم تجريده من موقع stackblitz . إذا نقرت على "CMS System" في شريط التنقل الأيسر ، فسيتم طرح استثناء ExpressionChangedAfterItHasBeenCheckedError .

في المثال ، يعمل admin.component كمكون رئيسي مسؤول عن 3 أشياء:

  • عرض التنقل الأيسر
  • شريط علوي يمكن للمكونات الفرعية استخدامه لعرض رسالة المؤشر الخاصة بهم
  • قسم محتوى رئيسي يعرض البيانات الخاصة بالمكون الفرعي من خلال <router-outlet></router-outlet> .

المكون الرئيسي

<div class="ticker-container" *ngIf="tickedMessage !== ''">
  <h1>{{tickedMessage}}</h1>
  <button type="button" (click)="tickedMessage = ''">
    close
  </button>
</div>

<div class="side-nav">
  <h2>side nav</h2>
  <ul>
    <li><a routerLink="/">dashboard</a></li>
    <li><a routerLink="/apps/thirdpartycms">CMS System</a></li>
  </ul>
</div>
<div class="main-content-container">
  <router-outlet></router-outlet>
</div>
export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      On the side nav if you click on CMS System, this piece of code throws the exception: 
      "ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed 
       after it was checked. Previous value: 'false'. Current value: 'true'.""
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe((tickedMessage: string) => {
      this.tickedMessage = tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

أثناء البحث عن كيفية توصيل البيانات احتياطيًا إلى المكون الرئيسي عند استخدام <router-outlet></router-outlet> ، قرأت أنه إذا استخدمت @Output لإرسال البيانات مرة أخرى إلى المكون الرئيسي ، سينتهي الأمر بتوجيه منفذ جهاز التوجيه التعامل مع الحدث. نظرًا لأنه حدث مخصص ، فلن يعرف توجيه منفذ جهاز التوجيه كيفية التعامل معه. لذلك تخطيت هذه الطريقة. بمجرد أن قرأت عن الخدمات المشتركة في angular.io ، كتبت ما يلي:

الخدمة المشتركة

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs/Subject';

@Injectable()
export class AdminService {

  private _tickedMessageAnnounced = new Subject<string>();

  tickedMessageAnnounced$ = this._tickedMessageAnnounced.asObservable();

  announceTickedMessage(tickedMessage: string) {
    this._tickedMessageAnnounced.next(tickedMessage);
  }
}

مرة واحدة من المكونات الفرعية تسمى this.adminService.announceTickedMessage(this._tickedMessage); :

مكون الطفل

import { Observable } from 'rxjs/Observable';
import { Component, ViewChild, OnInit, Inject } from '@angular/core';
import { AdminService } from '../../../core/admin/admin.service';

@Component({
  selector: 'cms',
  templateUrl: './thirdparty-cms.component.html',
  styleUrls: ['./thirdparty-cms.component.scss'],
})
export class ThirdPartyCmsComponent implements OnInit {
  private _tickedMessage: string;
  constructor(private adminService: AdminService) { }

  ngOnInit(): void {
    this._tickedMessage = 'Please do not create an entry for a page URL that does not exist in the CMS system';

    this.adminService.announceTickedMessage(this._tickedMessage);
  }
}

طرح المكون الرئيسي استثناء ExpressionChangedAfterItHasBeenCheckedError لأنه حاول تحديث إحدى خصائصه العامة بقيمة الرسالة المحددة التي يرسلها المكون الفرعي:

المكون الأصلي (يطرح استثناء)

export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      On the side nav if you click on CMS System, this piece of code throws the exception: 
      "ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed 
       after it was checked. Previous value: 'false'. Current value: 'true'.""
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe((tickedMessage: string) => {
      this.tickedMessage = tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

ما أصلح ExpressionChangedAfterItHasBeenCheckedError كان من خلال تنفيذ معاودة الاتصال بالاشتراك بشكل غير متزامن وانتظار الرسالة المحددة:

المكون الأصلي (مع انتظار غير متزامن)

export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      This clears the ExpressionChangedAfterItHasBeenCheckedError exception.
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe(async (tickedMessage: string) => {
      this.tickedMessage = await tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

قادمة من عالم WPF ، يبدو أننا بحاجة إلى إخبار مؤشر ترابط واجهة المستخدم بانتظار رد اتصال الاشتراك الذي يمكن ملاحظته حيث يتم تنفيذ الملاحظات بشكل غير متزامن؟

لقد بدأت للتو مع Angular لذا يرجى بعض التعاطف تجاه مهاراتي Angular: P.

ktabarez شكرًا ، لقد ساعدني هذا كثيرًا!

ktabarez شكرا! يبدو أن هذا يعمل بشكل جيد! كنت أقوم بلف الاشتراك في المكون الرئيسي في مهلة ، والتي كانت تعمل أيضًا ، لكنني شعرت بأنها قذرة.

أرى أنه عند استجواب حالة إعادة التشغيل ، من المضمون التسليم ولكن عند بدء التشغيل مثله عند وضعه في ngOnInit بدلاً من المنشئين

ktabarez شكرا جزيلا!

الحل الخاص بك async ... await in subscribe يعمل.

ktabarez يكمن الحل الذي

إذن ، السؤال: لماذا يتعين علينا إجراء عملية غير متزامنة / انتظار على حساب خاصية على خاصية يمكن ملاحظتها والتي تحصل على بيانات من النهاية الخلفية والتي قد تتغير بشكل متكرر؟ ماذا عن ngClass يجعل هذا ضروريًا؟ أحصل على هذا أثناء استخدام {يمكن ملاحظته | أنبوب غير متزامن}. لذا فإن عدم التزامن / الانتظار ليس أفضل تقييم بالنسبة لي. (أو الأنبوب غير المتزامن ليس أفضل طريقة)

لدي هذه المشكلة حيث يحتوي المكون الرئيسي الخاص بي على مكونات فرعية متعددة. يحتوي أحد المكوِّنات الفرعية على إدخال حيث يحتاج إلى بيانات من خاصية المكوِّن الأصلي ويحصل الموقع على بياناته من عنصر يمكن ملاحظته / وعد (جرب كليهما).

parent.component.html

<mat-tab-group>
   <mat-tab>
      <app-child-1></app-child-1>
   </mat-tab>
   <mat-tab>
      <app-child-2></app-child-2>
   </mat-tab>
   <mat-tab>
      <app-child-3 [datasource]="data"></app-child-3>
   </mat-tab>
</mat-tab-group>

parent.component.ts

// ... omitted...
data: any;

ngOnInit() {
   this.service1.getMethod(some_id).then(data => this.data = data);
}

جئت عبر كل الحلول الموجودة في الموضوع. لكن لا شيء يعمل في حالتي. لحسن الحظ ، حيث ألمح البعض إلى أن هذه مشكلة ناتجة عن معالجة DOM ، مثل وضع تعبير على _ (ngIf) _ كمثال.

لذا من أجل تجنب هذا ، أحاول تجربة قالب ng و ngTemplateOutlet .

<ng-container *ngTemplateOutlet="data ? content : loading"></ng-container>

<ng-template #content>
   <mat-tab-group>
      <mat-tab>
         <app-child-1></app-child-1>
      </mat-tab>
      <mat-tab>
         <app-child-2></app-child-2>
      </mat-tab>
      <mat-tab>
         <app-child-3 [datasource]="data"></app-child-3>
      </mat-tab>
   </mat-tab-group>
</ng-template>

<ng-template #loading>Loading your component. Please wait</ng-template>

تمكنت من حل هذا بدون استخدام ChangeDetectorRef أو setTimeout()

بالنسبة لي ، حدث هذا الخطأ عند إظهار / إخفاء الحقل وفقًا للحالة ، وتمكنت من حلها بعد تطبيق قيم إعداد الحقول:

this.form.get ('field').updateValueAndValidity();

لقد كنت أحاول القيام بذلك باستخدام مربع حوار mat يتم فتحه في init ، وقد جربت حرفياً كل حل رأيته في سلسلة الرسائل هذه (تعيين المهلة ، حل الوعد ، أنبوب يمكن ملاحظته ، بعد بدء العرض ، بعد بدء المحتوى) والتركيبات فيه. لا يزال لا يعمل. ثم رأيت مشاركة Splaktar حول كيفية انتهاك هذا لتدفق البيانات في اتجاه واحد ، لذلك قمت حرفيًا بعمل علامة في ngrx تفيد بفتح مربع الحوار والاشتراك في ذلك ، وهو خطأ في البداية وما زلت أتلقى هذا الخطأ. ماذا أفعل بحق الأرض؟ هل لا توجد طريقة فعلية لفتح حوار مشروط دون إرفاقه بحدث منفصل تمامًا؟

في التهيئة:
this.store.pipe( select(getShouldLogin), ).subscribe((shouldLogin: boolean) => { if (shouldLogin) { this.openLoginDialog(); } }); this.checkUserId();

وظائف مساعدة:
checkUserId() { const id = this.cookies.getUserId(); if (!id || id === 'undefined') { this.connApi.setShouldLogin(true); } }
openLoginDialog() { const dialogRef = this.dialog.open(LoginDialogComponent, { width: '400px', height: '300px', }); dialogRef.afterClosed().subscribe(result => this.handleLogin(result)); }

باستخدام الزاوية 6.0.0-rc.1

أنا أستخدم Angular مع ngrx وحصلت على هذا الخطأ بعد إرسال نموذج.
ثابت عن طريق الإعداد
ChangeDetectionStrategy.OnPush
في مكون النموذج

لا تزال تواجه المشكلة. جرب كل أنواع الحلول. setTimeout المرة الأولى ، ولكن يؤدي التشغيل اللاحق لـ dialog.open إلى نفس الخطأ. أي حالة على هذا؟

أواجه نفس المشكلة. حاولت كل شيء ولكن يبدو أن هذه المشكلة تنشأ في اتجاه واحد تدفق البيانات.
أنا هنا أحاول تحديث العناصر المحددة. حيث تحل المشكلة ولكن يظهر الخطأ نفسه.

        <p-row> <p-column class="text-left" footer="{{selectedWoids.length}} {{getWoidString(selectedWoids.length)}} selected out of {{getTotalCounts()}} {{getWoidString(getTotalCounts())}}" colspan="11"></p-column> </p-row>

مجموعة خاصة SelectedWoidsCount () {
إذا (this.isSelectAllWoids) {
this.selectedWoids = this.allWoids ؛
}
}

لأنك كسرت خط أنابيب العرض لـ Angular ...

تظهر لي أخطاء "ExpressionChanged ..." مشابهة بعد التبديل من 4.1.2 إلى 4.2.3 .

في حالتي ، لدي مكونان يتفاعلان عبر خدمة. يتم تقديم الخدمة عبر الوحدة النمطية الرئيسية الخاصة بي ، ويتم إنشاء ComponentA قبل ComponentB .

في 4.1.2 ، عملت البنية التالية على ما يرام دون أخطاء:

_ComponentA نموذج: _

<ul *ngIf="myService.showList">
...

_ComponentB فئة: _

ngOnInit() {
  this.myService.showList = false; //starts as true in the service
  ...
}

في 4.2.3 ، يجب علي تعديل قالب ComponentA النحو التالي لتجنب الخطأ "ExpressionChanged ...":

_ComponentA نموذج: _

<ul [hidden]="!myService.showList">
...

ما زلت أحاول معرفة سبب تحول هذا الأمر إلى مشكلة فقط ، ولماذا التبديل من *ngIf إلى [hidden] يعمل.

هل وجدت كيف يعمل عند التبديل من ngIf إلى [مخفي]

هل يوجد حل لهذه المشكلة في Angular 6 حتى الآن ؟؟

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

حقيقة أن نفس الحالة كانت تعمل في الإصدار 4.1.2 تتعلق ببعض الأخطاء التي تم التخلص منها في الإصدارات الأحدث ، بما في ذلك 4.2.3.

أنا أستخدم Angular 6. أستخدم خدمة MessageService للتواصل بين مكون المصادقة ومكوّن تطبيقي لعرض محتوى مختلف لشريط التنقل في حالة تسجيل دخول المستخدم (أو عدم تسجيله)

يعمل بشكل جيد بغض النظر عن هذا الخطأ!
لقد جربت حلولًا أخرى مثل EventEmitter لكنها لا تعمل! أو أحصل على نفس الخطأ.

dotNetAthlete ... اعرض عرضًا توضيحيًا بسيطًا لإعادة الإنتاج على Stackblitz. من الصعب التحدث عنها بدون الكود الحقيقي.

dotNetAthlete أنا أستخدم نفس الآلية الأساسية حيث لدي خدمة حالة تحتوي على عنوان صفحة كموضوع سلوك (asObservable) في مكون الحاوية الرئيسي ، والمكونات الفرعية التي تم تحميلها فيه والتي تحدد عنوان الصفحة المناسب في خدمة الحالة للحاوية الأم للمراقبة والعرض.

كان هناك دائمًا خطأ "تم تغيير التعبير" عندما قامت الحاوية بتهيئة القيمة ثم قام المكون الفرعي بتحديثها على الفور ، لذلك أفعل ذلك الآن في الأصل / الحاوية:

    this.stateSvc.currentPageTitle$
      .subscribe(
        (async (pageTitle) => {
          this.pageTitle = await pageTitle;
        }))

حيث يوجد هذا الجهاز الحكومي:

  // Store
  private currentPageTitleStore = new BehaviorSubject<string>("");

  // Getter (as Observable)
  public currentPageTitle$ = this.currentPageTitleStore.asObservable();

  // Setter
  public setCurrentPageTitle(pageTitle: string) {
    this.currentPageTitleStore.next(pageTitle);
  }

هذا يزيل المشكلة. يعتمد هذا الحل على بعض التعليقات المفيدة من الآخرين في هذا الموضوع.

alignsoft - حل جميل - المشكلة ثابتة في تطبيق Angular 6.

أرى هذا في إعادة تحميل الصفحة على الزاوية 7 الآن ...
أقوم بتعيين قيمة مكونات الطفل على الوالدين ngAfterViewInit
كان لا بد من القيام بذلك على المكون الفرعي

  public activate() {
    setTimeout(() => this.active = true);
  }

لقد وجدت الحل في هذا الموضوع https://github.com/angular/angular/issues/10762
بعد أن استخدمت ربط دورة الحياة AfterContentInit ، يختفي الخطأ.

alignsoft ، أضف فقط إلى التغيير الخاص بك ، لأننا نستخدم BehavioralSubject بدلاً من موضوع ،

صقلته على النحو التالي:

\\ Component
export class AppComponent implements OnInit {
  isLoading: Boolean;

  constructor(private loaderService: LoaderService) { }

  ngOnInit(){
    this.loaderService.isLoading.subscribe(async data => {
      this.isLoading = await data;
    });
  }
}
\\ Service
@Injectable({
  providedIn: 'root'
})
export class LoaderService {
  // A BehaviorSubject is an Observable with a default value
  public isLoading: BehaviorSubject<Boolean> = new BehaviorSubject(false);
  constructor() {}
}
\\ Any other component or in my case, an interceptor that has the LoaderService injected
this.loaderService.isLoading.next(true);

لكنني أتفق مع daddyschmack ، أود الحصول على أي مورد يشير إلى معلومات تتعلق مع أي من الإصلاحات المذكورة أعلاه ( cdr.detectionChanges() أو async await أو aftercontentchecked ) ، لماذا تعمل السمة [hidden] وليس *ngIf

acidghost شكرًا لك على هذا الرابط ، لقد حل مشكلتي. لقد تخليت بالفعل عن محاولة إصلاحها في منطقة واحدة من طلبي ولكن عندما بدأت في الظهور مرة أخرى في منطقة جديدة ...

تم قفل هذه المشكلة تلقائيًا بسبب عدم النشاط.
يرجى تقديم مشكلة جديدة إذا كنت تواجه مشكلة مشابهة أو ذات صلة.

اقرأ المزيد حول سياسة قفل المحادثة التلقائي .

_تم تنفيذ هذا الإجراء تلقائيًا بواسطة روبوت.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات