TypeScript - تغير التعبير ___ بعد أن تم التحقق منه
لماذا المكوّن في هذا البلانك البسيط
@Component({
selector: 'my-app',
template: `<div>I'm {{message}} </div>`,
})
export class App {
message:string = 'loading :(';
ngAfterViewInit() {
this.updateMessage();
}
updateMessage(){
this.message = 'all done loading :)'
}
}
الرمي:
استثناء: التعبير 'أنا {{message}} في App@0:5' قد تغير بعد التحقق منه. القيمة السابقة: 'أنا أحمل :( '. القيمة الحالية: 'لقد انتهيت من التحميل :) ' في [أنا {{message}} في App@0:5]
عندما كل ما أفعله هو تحديث ارتباط بسيط عند بدء عرضي؟
الإجابة رقم 1
كما ذكر drewmoore، الحل المناسب في هذه الحالة هو تشغيل كشف التغيير يدويًا للمكون الحالي. يتم ذلك باستخدام طريقة detectChanges() الخاصة بالكائن ChangeDetectorRef (المستوردة من angular2/core)، أو باستخدام طريقتها markForCheck()، والتي تجعل أيضًا أي مكونات أصلية يتم تحديثها. المثال ذي الصلة:
import { Component, ChangeDetectorRef, AfterViewInit } from 'angular2/core'
@Component({
selector: 'my-app',
template: `<div>I'm {{message}} </div>`,
})
export class App implements AfterViewInit {
message: string = 'loading :(';
constructor(private cdr: ChangeDetectorRef) {}
ngAfterViewInit() {
this.message = 'all done loading :)'
this.cdr.detectChanges();
}
}
إليك أيضًا بعض Plunkers التي توضح أساليب ngOnInit و setTimeout و enableProdMode فقط في حال الحاجة.
الإجابة رقم 2
أولاً، لاحظ أن هذا الاستثناء سيحدث فقط عندما تقوم بتشغيل تطبيقك في وضع التطوير (وهو الحال بشكل افتراضي اعتبارًا من beta-0): إذا قمت باستدعاء enableProdMode() عند تمهيد التطبيق، فلن يحدث هذا الاستثناء (انظر plunk المحدث).
ثانيًا، لا تفعل ذلك لأن هذا الاستثناء يتم إلقاؤه لسبب وجيه: باختصار، عندما تكون في وضع التطوير، يتم بعد كل جولة من الكشف عن التغييرات مباشرة جولة ثانية تتحقق من أن جميع الربط لم يتغير منذ نهاية الجولة الأولى، حيث أن هذا قد يشير إلى أن التغييرات يتم إحداثها بواسطة الكشف عن التغييرات نفسه.
في المثال الخاص بك، يتم تغيير الربط {{message}} بواسطة استدعائك لـ setMessage()، والذي يحدث في خطاف ngAfterViewInit، والذي يحدث كجزء من دورة الكشف عن التغييرات الأولية. هذا في حد ذاته ليس مشكلة - المشكلة هي أن setMessage() يغير الربط ولكنه لا يطلق دورة جديدة من الكشف عن التغييرات، ما يعني أن هذا التغيير لن يتم اكتشافه حتى يتم تفعيل دورة الكشف عن التغييرات في مكان آخر في المستقبل.
الخلاصة: أي شيء يغير الربط يحتاج إلى تشغيل جولة من الكشف عن التغييرات عند حدوثه.
تحديث استجابة لجميع الطلبات لمثال عن كيفية القيام بذلك: حل @Tycho يعمل، وكذلك الثلاث طرق في الإجابة التي أشار إليها @MarkRajcok. لكن بصراحة، كلها تبدو لي قبيحة وخاطئة، مثل نوع الحيل التي اعتدنا الاعتماد عليها في ng1.
للتأكد، هناك ظروف عرضية حيث تكون هذه الحيل مناسبة، لكن إذا كنت تستخدمها أكثر من مرة عرضية جدًا، فهذا علامة على أنك تقاوم الإطار بدلاً من احتضان طبيعته التفاعلية بالكامل.
في رأيي المتواضع، الطريقة الأكثر تعبيرًا، و"بطريقة Angular2" للتعامل مع هذا هي شيء على غرار: (plunk)
@Component({
selector: 'my-app',
template: `<div>I'm {{message | async}} </div>`
})
export class App {
message:Subject<string> = new BehaviorSubject('loading :(');
ngAfterViewInit() {
this.message.next('all done loading :)')
}
}
الإجابة رقم 3
لقد نجح ngAfterViewChecked() بالنسبة لي:
import { Component, ChangeDetectorRef } from '@angular/core'; //import ChangeDetectorRef
constructor(private cdr: ChangeDetectorRef) { }
ngAfterViewChecked(){
//your code to update the model
this.cdr.detectChanges();
}
الإجابة رقم 4
لقد قمت بإصلاح ذلك عن طريق إضافة ChangeDetectionStrategy من نواة Angular.
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'page1',
templateUrl: 'page1.html',
})
الإجابة رقم 5
ألا يمكنك استخدام ngOnInit لأنك فقط تقوم بتغيير المتغير العضوي message؟
إذا كنت تريد الوصول إلى مرجع لمكوّن فرعي @ViewChild(ChildComponent)، فأنت بحاجة فعلياً إلى الانتظار له باستخدام ngAfterViewInit.
الحل القذر هو استدعاء updateMessage() في دورة الحدث التالية باستخدام مثلاً setTimeout.
ngAfterViewInit() {
setTimeout(() => {
this.updateMessage();
}, 1);
}
الإجابة رقم 6
لهذا جربت الإجابات أعلاه العديد منها لا يعمل في النسخة الأخيرة من Angular (6 أو الأحدث)
أنا أستخدم تحكم Material الذي يتطلب إجراء تغييرات بعد الانتهاء من الربط الأول.
export class AbcClass implements OnInit, AfterContentChecked{
constructor(private ref: ChangeDetectorRef) {}
ngOnInit(){
// your tasks
}
ngAfterContentChecked() {
this.ref.detectChanges();
}
}
إضافة إجابتي لذلك، هذا يساعد بعض الأشخاص في حل مشكلة محددة.
الإجابة رقم 7
انتقلت من AfterViewInit إلى AfterContentChecked وقد نجح ذلك بالنسبة لي.
إليك العملية
أضف التبعية في منشئك:
constructor (private cdr: ChangeDetectorRef) {}واستدعِ تسجيل الدخول الخاص بك في كود الطريقة المنفذة هنا:
ngAfterContentChecked() { this.cdr.detectChanges(); // call or add here your code }
الإجابة رقم 8
تشرح المقالة كل ما تحتاج لمعرفته عن خطأ ExpressionChangedAfterItHasBeenCheckedError السلوك بالتفصيل الكبير.
المشكلة في إعدادك هي أن دالة دورة الحياة ngAfterViewInit يتم تنفيذها بعد معالجة التغييرات في DOM. وأنت في الواقع تقوم بتغيير الخاصية التي تُستخدم في القالب في هذه الدالة، مما يعني أن DOM يحتاج إلى إعادة العرض:
ngAfterViewInit() {
this.message = 'all done loading :)'; // needs to be rendered the DOM
}
وهذا سيتطلب دورة كشف تغييرات أخرى، وAngular بموجب التصميم يشغل دورة هضم واحدة فقط.
لديك في الأساس خياران لكيفية إصلاحه:
تحديث الخاصية بشكل غير متزامن إما باستخدام
setTimeout، أوPromise.then، أو المراقب غير المتزامن المشار إليه في القالبقم بتنفيذ تحديث الخاصية في هوك قبل تحديث DOM - ngOnInit، ngDoCheck، ngAfterContentInit، ngAfterContentChecked.
الإجابة رقم 9
يحدث هذا الخطأ لأن القيمة الموجودة يتم تحديثها مباشرة بعد تهيئتها. لذلك إذا قمت بتحديث القيمة الجديدة بعد عرض القيمة الموجودة في DOM، فسيعمل بشكل جيد. كما هو مذكور في هذا المقال تصحيح أخطاء Angular "تغير التعبير بعد التحقق منه"
على سبيل المثال يمكنك استخدام
ngOnInit() {
setTimeout(() => {
//code for your new value.
});
}
أو
ngAfterViewInit() {
this.paginator.page
.pipe(
startWith(null),
delay(0),
tap(() => this.dataSource.loadLessons(...))
).subscribe();
}
كما ترى، لم أذكر الوقت في طريقة setTimeout. نظرًا لأنها واجهة برمجة تطبيقات مقدمة من المتصفح، وليست واجهة برمجة تطبيقات JavaScript، فسيتم تشغيل هذا بشكل منفصل في مكدس المتصفح وسينتظر حتى يتم الانتهاء من عناصر مكدس الاستدعاء.
كيف يفسر فيليب روبرتس مفهوم استدعاء واجهة برمجة تطبيقات المتصفح في أحد فيديوهات يوتيوب (ما هو حلقة الأحداث؟).
الإجابة رقم 10
كل ما عليك فعله هو تحديث رسالتك في جزء دورة الحياة الصحيح، في هذه الحالة هو ngAfterContentChecked بدلاً من ngAfterViewInit، لأن في ngAfterViewInit تم بدء التحقق من المتغير message ولكنه لم ينته بعد.
انظر: https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#afterview
لذا سيكون الكود فقط:
import { Component } from 'angular2/core'
@Component({
selector: 'my-app',
template: `<div>I'm {{message}} </div>`,
})
export class App {
message: string = 'loading :(';
ngAfterContentChecked() {
this.message = 'all done loading :)'
}
}
شاهد العرض التجريبي العامل على Plunker.
