Angular2 को प्रबंधित करने के लिए कैसे "अभिव्यक्ति की जाँच के बाद बदल गया है" अपवाद जब एक घटक संपत्ति वर्तमान डेटाटाइम पर निर्भर करती है


168

मेरे घटक में ऐसी शैलियाँ हैं जो वर्तमान डेटाटाइम पर निर्भर करती हैं। मेरे घटक में मुझे निम्न कार्य मिला है।

  private fontColor( dto : Dto ) : string {
    // date d'exécution du dto
    let dtoDate : Date = new Date( dto.LastExecution );

    (...)

    let color =  "hsl( " + hue + ", 80%, " + (maxLigness - lightnessAmp) + "%)";

    return color;
  }

lightnessAmpवर्तमान डेटाइम से गणना की जाती है। यदि dtoDateपिछले 24 घंटों में रंग बदल गया है।

सटीक त्रुटि निम्नलिखित है:

जाँच के बाद अभिव्यक्ति बदल गई है। पिछला मान: 'hsl (123, 80%, 49%)'। वर्तमान मूल्य: 'एचएसएल (123, 80%, 48%)'

मुझे पता है कि केवल मूल्य की जाँच होने पर ही विकास मोड में अपवाद दिखाई देता है। यदि चेक किया गया मान अपडेट किए गए मान से भिन्न है, तो अपवाद फेंक दिया गया है।

इसलिए मैंने अपवाद को रोकने के लिए निम्न हुक विधि में प्रत्येक जीवनचक्र पर वर्तमान डेटाइम को अद्यतन करने का प्रयास किया:

  ngAfterViewChecked()
  {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
  }

... लेकिन सफलता के बिना।


जवाबों:


357

परिवर्तन के बाद स्पष्ट रूप से परिवर्तन का पता लगाएं:

import { ChangeDetectorRef } from '@angular/core';

constructor(private cdRef:ChangeDetectorRef) {}

ngAfterViewChecked()
{
  console.log( "! changement de la date du composant !" );
  this.dateNow = new Date();
  this.cdRef.detectChanges();
}

सही समाधान, धन्यवाद। मैंने देखा कि यह निम्नलिखित हुक विधियों के साथ भी काम करता है: ngOnChanges, ngDoCheck, ngAfterContentChecked। तो क्या कोई सबसे अच्छा विकल्प है?
एंथनी ब्रेनेलीयर

28
यह आपके उपयोग के मामले पर निर्भर करता है। यदि आप कुछ करना चाहते हैं जब एक घटक को इनिशियलाइज़ किया जाता है, ngOnInit()तो आमतौर पर पहला स्थान होता है। यदि कोड DOM के रेंडर होने पर निर्भर करता है ngAfterViewInit()या ngAfterContentInit()अगले विकल्प हैं। ngOnChanges()यदि कोड हर बार इनपुट बदला गया हो तो एक अच्छा फिट है। ngDoCheck()कस्टम परिवर्तन का पता लगाने के लिए है। वास्तव में मुझे नहीं पता कि इसके ngAfterViewChecked()लिए सबसे अच्छा उपयोग क्या है। मुझे लगता है कि यह सिर्फ पहले या बाद में कहा जाता है ngAfterViewInit()
गुंटर ज़ोचबॉयर

2
@KushalJayswal क्षमा करें, आपके विवरण से कोई मतलब नहीं हो सकता। मैं उस कोड के साथ एक नया प्रश्न बनाने का सुझाव दूंगा जो यह दर्शाता है कि आप क्या करने की कोशिश करते हैं। आदर्श रूप से एक स्टैकब्लिट्ज उदाहरण के साथ।
गुंटर ज़ोचाउर

4
यह एक उत्कृष्ट समाधान भी है यदि आपका घटक राज्य ब्राउज़र-गणना डोम गुणों पर आधारित है, जैसे clientWidth, आदि
जोनाह

1
बस ngAfterViewInit पर इस्तेमाल किया, एक आकर्षण की तरह काम किया।
फ्रांसिस्को आरलेओ

42

टी एल; डॉ

ngAfterViewInit() {
    setTimeout(() => {
        this.dateNow = new Date();
    });
}

यद्यपि यह एक समाधान है, कभी-कभी किसी भी अच्छे तरीके से इस मुद्दे को हल करना मुश्किल होता है, इसलिए यदि आप इस दृष्टिकोण का उपयोग कर रहे हैं तो अपने आप को दोष न दें। वह ठीक है।

उदाहरण : प्रारंभिक अंक [ लिंक ], के साथ हलsetTimeout() [ लिंक ] के


कैसे बचें

सामान्य तौर पर यह त्रुटि आमतौर पर आपके द्वारा कहीं न कहीं जुड़ने के बाद भी होती है (यहां तक ​​कि माता-पिता / बच्चे के घटकों में भी) ngAfterViewInit । तो पहला सवाल अपने आप से पूछना है - क्या मैं बिना जी सकता हूं ngAfterViewInit? शायद आप कोड को कहीं ले जाएं ( ngAfterViewCheckedएक विकल्प हो सकता है)।

उदाहरण : [ लिंक ]


भी

इसके अलावा एसिंक्स का सामान ngAfterViewInitजो डोम को प्रभावित करता है, इसका कारण हो सकता है। पाइप में ऑपरेटर को setTimeoutजोड़कर या उसके द्वारा हल किया जा सकता है delay(0):

ngAfterViewInit() {
  this.foo$
    .pipe(delay(0)) //"delay" here is an alternative to setTimeout()
    .subscribe();
}

उदाहरण : [ लिंक ]


अच्छा पढ़ना

यह कैसे और क्यों यह डिबग करने के बारे में अच्छा लेख: लिंक


2
चयनित समाधान से धीमा
पीट बी

6
यह सबसे अच्छा समाधान नहीं है, लेकिन लानत यह हमेशा काम करता है। चयनित उत्तर हमेशा काम नहीं करता है (किसी को हुक को समझने के लिए इसे काम करने की आवश्यकता है)
फ्रेडी बोंडा

सही स्पष्टीकरण क्या है क्यों यह काम करता है, किसी को पता है? क्या यह इसलिए है क्योंकि यह एक अलग थ्रेड (async) में चलता है?
knnhcn

3
@knnhcn जावास्क्रिप्ट में विभिन्न धागे जैसी कोई चीज नहीं है। जेएस प्रकृति द्वारा एकल-पिरोया गया है। टाइमर समाप्त होने के कुछ समय बाद सेटटाइमआउट इंजन को फंक्शन निष्पादित करने के लिए कहता है यहाँ टाइमर 0 है, जो वास्तव में आधुनिक ब्राउज़रों में 4 के रूप में माना जाता है, जो कोणीय समय के लिए अपनी जादुई
चीज़ों

27

जैसा कि गीथूब मुद्दे पर @leocaseiro ने उल्लेख किया है

मुझे उन लोगों के लिए 3 समाधान मिले जो आसान सुधारों की तलाश में हैं।

1) से चल रहा है ngAfterViewInit हैngAfterContentInit

2) # 14748 (सुझाव ) ngAfterViewCheckedके ChangeDetectorRefरूप में सुझाए गए के साथ संयुक्त रूप से आगे बढ़ना

3) ngOnInit () के साथ रखें लेकिन ChangeDetectorRef.detectChanges()अपने परिवर्तनों के बाद कॉल करें ।


1
क्या कोई भी दस्तावेज दे सकता है कि सबसे अनुशंसित उपाय क्या है?
पिपो

13

उसके तुम दो उपाय करो!

1. OnDush में ChangeDetectionStrategy को संशोधित करें

इस समाधान के लिए, आप मूल रूप से कोणीय बता रहे हैं:

परिवर्तनों के लिए जाँच बंद करो; मैं इसे केवल तभी करूँगा जब मुझे पता होगा कि यह आवश्यक है

जल्दी ठीक:

अपने घटक को संशोधित करें ताकि इसका उपयोग हो ChangeDetectionStrategy.OnPush

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
    // ...
}

इसके साथ, चीजें अब काम नहीं करती हैं। ऐसा इसलिए है क्योंकि अब से आपको detectChanges()मैन्युअल रूप से कोणीय कॉल करना होगा।

this.cdr.detectChanges();

यहाँ एक लिंक है जिसने मुझे ChangeDetectionStrategy को सही समझने में मदद की : https://alligator.io/angular/change-detection-strategy/

2. अभिव्यक्ति को समझना

इस त्रुटि के कारणों के बारे में tomonari_t उत्तर से एक छोटा सा उद्धरण है , मैंने केवल उन हिस्सों को शामिल करने की कोशिश की है जिन्होंने मुझे इसे समझने में मदद की।

पूरा लेख यहां दिखाए गए प्रत्येक बिंदु के बारे में वास्तविक कोड उदाहरण दिखाता है।

मूल कारण कोणीय जीवनचक्र है:

प्रत्येक ऑपरेशन के बाद कोणीय को याद है कि ऑपरेशन करने के लिए किन मूल्यों का उपयोग किया जाता है। वे घटक दृश्य के पुरानेवैल्यू गुण में संग्रहीत हैं।

सभी घटकों के लिए जाँच किए जाने के बाद, कोणीय फिर अगला पाचन चक्र शुरू करता है, लेकिन ऑपरेशन करने के बजाय यह वर्तमान मानों की तुलना उन लोगों के साथ करता है जिन्हें यह पिछले पाचन चक्र से याद है।

निम्नलिखित कार्य पाचन चक्र में जाँचे जा रहे हैं:

जाँच करें कि बाल घटकों को दिए गए मान वही हैं जो इन घटकों के गुणों को अपडेट करने के लिए उपयोग किए जाते हैं।

जाँच करें कि DOM तत्वों को अद्यतन करने के लिए उपयोग किए जाने वाले मान वही हैं जो इन तत्वों को अद्यतन करने के लिए उपयोग किए जाने वाले मान अब समान हैं।

सभी बाल घटकों की जाँच

और इसलिए, त्रुटि को फेंक दिया जाता है जब तुलनात्मक मूल्य भिन्न होते हैं। , ब्लॉगर मैक्स कोर्सेटस्की ने कहा:

अपराधी हमेशा बाल घटक या एक निर्देश है।

और अंत में यहां कुछ वास्तविक दुनिया के नमूने हैं जो आमतौर पर इस त्रुटि का कारण बनते हैं:

  • साझा सेवाएँ
  • तुल्यकालिक घटना प्रसारण
  • गतिशील घटक तात्कालिकता

हर नमूना यहां पाया जा सकता है (प्लंकर), मेरे मामले में समस्या एक गतिशील घटक इंस्टेंटेशन थी।

इसके अलावा, अपने स्वयं के अनुभव से मैं हर किसी को setTimeoutसमाधान से बचने के लिए दृढ़ता से सलाह देता हूं, मेरे मामले में "लगभग" अनंत लूप (21 कॉल जो मैं आपको दिखाने के लिए तैयार नहीं हूं कि उन्हें कैसे उत्तेजित करना है) का कारण बना।

मैं हमेशा कोणीय जीवन-चक्र को ध्यान में रखने की सलाह दूंगा ताकि आप इस बात को ध्यान में रख सकें कि जब आप किसी अन्य घटक के मूल्य को संशोधित करते हैं तो वे हर बार कैसे प्रभावित होंगे। इस त्रुटि के साथ कोणीय आपको बता रहा है:

आप शायद इसे गलत तरीके से कर रहे हैं, क्या आप सुनिश्चित हैं कि आप सही हैं?

वही ब्लॉग भी कहता है:

अक्सर, डायनेमिक कंपोनेंट बनाने के लिए सही बदलाव डिटेक्शन हुक का उपयोग करना होता है

मेरे लिए एक छोटा गाइड कोडिंग करते समय कम से कम निम्नलिखित दो बातों पर विचार करना है ( मैं समय के साथ इसे पूरक करने की कोशिश करूंगा ):

  1. माता-पिता के घटक मानों को बच्चे के घटकों से संशोधित करने से बचें, इसके बजाय: उन्हें इसके माता-पिता से संशोधित करें।
  2. जब आप का उपयोग करें @Inputऔर@Output निर्देश देते हैं, तब तक lyfecycle परिवर्तनों को ट्रिगर करने से बचने की कोशिश करते हैं जब तक कि घटक पूरी तरह से आरंभिक नहीं हो जाता।
  3. अनावश्यक कॉल से बचें this.cdr.detectChanges(); , अधिक त्रुटियों को ट्रिगर कर सकते हैं, विशेषकर तब जब आप बहुत सारे गतिशील डेटा से निपट रहे हों
  4. जब उपयोग this.cdr.detectChanges();अनिवार्य हो तो सुनिश्चित करें कि @Input, @Output, etcउपयोग किए जा रहे चर ( ) सही पहचान हुक पर भरे / आरंभ किए गए हैं ( OnInit, OnChanges, AfterView, etc)
  5. जब संभव हो, छिपाने के बजाय हटा दें , यह बिंदु 3 और 4 से संबंधित है।

भी

यदि आप कोणीय जीवन हुक को पूरी तरह से समझना चाहते हैं, तो मैं आपको आधिकारिक दस्तावेज पढ़ने की सलाह देता हूं:


मैं अभी भी नहीं समझ सकता क्यों बदल रहा है ChangeDetectionStrategyकरने के लिए OnPushमेरे लिए तय यह। मेरे पास एक साधारण घटक है, जिसमें है [disabled]="isLastPage()"। वह तरीका MatPaginator-ViewChild पढ़ रहा है और वापस लौट रहा है this.paginator !== undefined ? this.paginator.pageIndex === this.paginator.getNumberOfPages() - 1 : true;। पेजिनेटर अभी उपलब्ध नहीं है, लेकिन इसके उपयोग के बाद बाध्य किया गया है @ViewChildChangeDetectionStrategyहटाए गए त्रुटि को बदलना - और कार्यक्षमता अभी भी पहले की तरह मौजूद है। मुझे यकीन नहीं है कि मेरे पास अब कौन सी कमियां हैं, लेकिन धन्यवाद!
इगोर

वह महान @Igor है! उपयोग की एकमात्र वापसी OnPushयह है कि आपको this.cdr.detectChanges()हर उस समय का उपयोग करना होगा जिसे आप ताज़ा करना चाहते हैं। मुझे लगता है कि आप पहले से ही इसका इस्तेमाल कर रहे थे
लुइस लिमास

9

हमारे मामले में हमने घटक में चेंजडिटेशन जोड़कर FIXED किया है और कॉल डिटेक्शन () में ngAfterContentChecked, कोड निम्नानुसार है।

@Component({
  selector: 'app-spinner',
  templateUrl: './spinner.component.html',
  styleUrls: ['./spinner.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SpinnerComponent implements OnInit, OnDestroy, AfterContentChecked {

  show = false;

  private subscription: Subscription;

  constructor(private spinnerService: SpinnerService, private changeDedectionRef: ChangeDetectorRef) { }

  ngOnInit() {
    this.subscription = this.spinnerService.spinnerState
      .subscribe((state: SpinnerState) => {
        this.show = state.show;
      });
  }

  ngAfterContentChecked(): void {
      this.changeDedectionRef.detectChanges();
  }

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

}

2

मुझे लगता है कि सबसे अच्छा और साफ समाधान आप सोच सकते हैं कि यह है:

@Component( {
  selector: 'app-my-component',
  template: `<p>{{ myData?.anyfield }}</p>`,
  styles: [ '' ]
} )
export class MyComponent implements OnInit {
  private myData;

  constructor( private myService: MyService ) { }

  ngOnInit( ) {
    /* 
      async .. await 
      clears the ExpressionChangedAfterItHasBeenCheckedError exception.
    */
    this.myService.myObservable.subscribe(
      async (data) => { this.myData = await data }
    );
  }
}

Angular 5.2.9 के साथ परीक्षण किया गया


3
यह हैकी है .. और इतना गैर-ज़रूरी है।
जोएरीशोएबी

1
@JoeriShoeby सभी अन्य समाधान ऊपर setTimeouts या उन्नत कोणीय घटनाओं पर आधारित कामकाज से जुड़े हैं ... इस समाधान शुद्ध सभी बड़ी कंपनियों द्वारा समर्थित ES2017 है ब्राउज़रों developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... caniuse.com / # खोज = प्रतीक्षा
जेवियरफ़ेन्स

1
यदि आप उदाहरण के लिए कोणीय + कॉर्डोवा का उपयोग करके एक मोबाइल एप्लिकेशन (एंड्रॉइड एपीआई 17 को लक्षित करते हुए) का निर्माण कर रहे हैं, तो आप ES2017 सुविधाओं पर भरोसा नहीं कर सकते हैं? ध्यान दें कि स्वीकृत उत्तर एक समाधान है, न कि एक काम के आस-पास ..
JoeriShoeby

@JoeriShoeby टाइपस्क्रिप्ट का उपयोग करते हुए, इसे JS में संकलित किया जाता है, इसलिए कोई सुविधा समर्थन समस्या नहीं है।
15

@biggbest आपका क्या मतलब है? मुझे पता है कि टाइपस्क्रिप्ट को जेएस के लिए संकलित किया जाएगा, लेकिन यह आपको सभी जेएस काम करने में सक्षम नहीं बनाता है। ध्यान दें कि जावास्क्रिप्ट एक वेबब्रोसर में चलाया जाता है, और हर ब्राउज़र उस पर अलग व्यवहार करता है।
जोरीशोएबी

2

मेरे आसपास एक छोटा सा काम कई बार इस्तेमाल किया

Promise.resolve(null).then(() => {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
    this.cdRef.detectChanges();
});

मैं मुख्य रूप से नियंत्रक में उपयोग किए जाने वाले कुछ चर के साथ "अशक्त" को प्रतिस्थापित करता हूं।


1

हालाँकि पहले से ही कई उत्तर हैं और परिवर्तन का पता लगाने के एक बहुत अच्छे लेख की एक कड़ी है, मैं यहाँ अपने दो सेंट देना चाहता था। मुझे लगता है कि चेक एक कारण के लिए है इसलिए मैंने अपने ऐप की वास्तुकला के बारे में सोचा और महसूस किया कि दृश्य में परिवर्तन का उपयोग करके BehaviourSubjectऔर सही जीवन चक्र हुक से निपटा जा सकता है । तो यहां मैंने एक समाधान के लिए क्या किया।

  • मैं एक थर्ड पार्टी कंपोनेंट ( फुलकेन्डर) का उपयोग करता हूं, लेकिन मैं कोणीय सामग्री का भी उपयोग करता हूं , इसलिए हालांकि मैंने स्टाइल के लिए एक नया प्लगइन बनाया है, लेकिन लुक और फील मिलना थोड़ा अजीब था क्योंकि रेपो को फोर्क किए बिना कैलेंडर हेडर का कस्टमाइजेशन संभव नहीं है। और अपना खुद का रोल करना।
  • इसलिए मैंने अंतर्निहित जावास्क्रिप्ट वर्ग प्राप्त किया, और घटक के लिए अपने स्वयं के कैलेंडर हेडर को इनिशियलाइज़ करने की आवश्यकता थी। यह ViewChildमेरे माता-पिता द्वारा प्रदान की जाने वाली befor प्रदान करने की आवश्यकता है, जिस तरह से कोणीय काम नहीं करता है। यही कारण है कि मैंने अपने टेम्पलेट में अपने लिए आवश्यक मूल्य को एक में लपेट दिया BehaviourSubject<View>(null):

    calendarView$ = new BehaviorSubject<View>(null);

अगला, जब मुझे यकीन है कि दृश्य की जाँच की जा सकती है, तो मैं उस विषय को @ViewChildनिम्न से अपडेट करता हूं :

  ngAfterViewInit(): void {
    // ViewChild is available here, so get the JS API
    this.calendarApi = this.calendar.getApi();
  }

  ngAfterViewChecked(): void {
    // The view has been checked and I know that the View object from
    // fullcalendar is available, so emit it.
    this.calendarView$.next(this.calendarApi.view);
  }

फिर, मेरे टेम्पलेट में, मैं बस asyncपाइप का उपयोग करता हूं । परिवर्तन का पता लगाने के साथ कोई हैकिंग, कोई त्रुटि नहीं, आसानी से काम करता है।

कृपया पूछने में संकोच न करें कि क्या आपको अधिक विवरण की आवश्यकता है।


1

त्रुटि से बचने के लिए एक डिफ़ॉल्ट फ़ॉर्म मान का उपयोग करें।

NgAfterViewInit () (जो भी मेरे मामले में त्रुटि को हल किया है) को लागू करने का पता लगाने के स्वीकृत उत्तर का उपयोग करने के बजाय, मैंने डायनेमिक रूप से आवश्यक फॉर्म फ़ील्ड के लिए डिफ़ॉल्ट मान को बचाने के बजाय निर्णय लिया, ताकि बाद में जब फॉर्म अपडेट हो जाए तो यदि उपयोगकर्ता नए आवश्यक फ़ील्ड को ट्रिगर करता है (और सबमिट बटन को अक्षम करने का कारण बनता है) पर एक विकल्प को बदलने का निर्णय लेता है तो यह वैधता नहीं बदली जाती है।

इसने मेरे घटक में एक छोटे से कोड को बचाया, और मेरे मामले में त्रुटि को पूरी तरह से टाला गया।


यह वास्तव में अच्छा अभ्यास है, इसके साथ आप एक अनावश्यक कॉल से बचेंthis.cdr.detectChanges()
लुइस लिमास

1

मुझे वह त्रुटि मिली क्योंकि मैंने एक चर घोषित किया था और बाद में
इसका उपयोग करके मूल्य बदलना चाहता थाngAfterViewInit

export class SomeComponent {

    header: string;

}

यह तय करने के लिए कि मैं कहां से आया हूं

ngAfterViewInit() { 

    // change variable value here...
}

सेवा

ngAfterContentInit() {

    // change variable value here...
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.