28

मैं इस त्रुटि के बारे में कुछ पढ़ने और जांच किया है, लेकिन यकीन नहीं क्या सही जवाब मेरी स्थिति के लिए है के साथ div चौड़ाई के बाइंडिंग। मैं समझता हूं कि देव मोड में, पहचान का पता दो बार चलता है, लेकिन मैं समस्या को हल करने के लिए enableProdMode() का उपयोग करने में अनिच्छुक हूं।Angular2 - के बाद यह जांच की गई अभिव्यक्ति बदल गया है - आकार बदलने की घटनाओं

यहाँ एक सरल उदाहरण है जहाँ तालिका में कोशिकाओं की संख्या के रूप में div की चौड़ाई का विस्तार वृद्धि हो। (ध्यान दें कि div की चौड़ाई सिर्फ स्क्रीन की चौड़ाई का कार्य नहीं है, इसलिए @Media आसानी से लागू नहीं किया जा सकता है)

मेरे HTML के रूप में (widget.template.html) इस प्रकार है:

<div #widgetParentDiv class="Content"> 
<p>Sample widget</p> 
<table><tr> 
    <td>Value1</td> 
    <td *ngIf="widgetParentDiv.clientWidth>350">Value2</td> 
    <td *ngIf="widgetParentDiv.clientWidth>700">Value3</td> 
</tr></table> 

यह अपने आप पर कुछ भी नहीं करता है। मुझे लगता है कि ऐसा इसलिए है क्योंकि परिवर्तन का पता लगाने के कारण कुछ भी नहीं हो रहा है। हालांकि, जब मैं निम्नलिखित करने के लिए पहली पंक्ति बदलने के लिए, और कॉल प्राप्त करने के एक खाली समारोह बनाने के लिए, यह काम कर रहा शुरू होता है, लेकिन कभी-कभी मैं

<div #widgetParentDiv class="Content"> 
    gets replaced with 
     <div #widgetParentDiv (window:resize)=parentResize(10) class="Content"> 

मेरे सबसे अच्छा 'के बाद यह त्रुटि जांच की गई अभिव्यक्ति बदल गया है' मिल अनुमान है कि इस संशोधन के साथ, पता लगाने का पता लगाना शुरू हो गया है और सबकुछ जवाब देना शुरू कर देता है, हालांकि, जब चौड़ाई तेजी से बदलती है तो अपवाद फेंक दिया जाता है क्योंकि परिवर्तन की पहचान के पिछले पुनरावृत्ति को div की चौड़ाई बदलने से अधिक पूरा करने में अधिक समय लगता है।

  1. क्या परिवर्तन पहचान को ट्रिगर करने के लिए कोई बेहतर तरीका है?
  2. मैं एक समारोह के माध्यम से आकार बदलने घटना पर कब्जा करने परिवर्तन पता तब लगता सुनिश्चित करने के लिए किया जाना चाहिए?
  3. #widthParentDiv का उपयोग कर स्वीकार्य div की चौड़ाई तक पहुँचने के लिए है?
  4. क्या कोई बेहतर समग्र समाधान है?

मेरी परियोजना पर अधिक जानकारी के लिए कृपया this इसी तरह के प्रश्न देखें।

धन्यवाद

+0

मुझे यकीन है कि मैं समझता हूँ कि आप क्या ढूंढ रहे हैं ... और अभी तक नहीं कर रहा हूँ ... सबसे पहले, तुम क्यों लिख सकता हूँ '* ngif =" ... 'यह एक सही वाक्यविन्यास हो सकता है (हालांकि इसे जांचने के लिए परेशान नहीं किया गया), लेकिन मुझे यकीन है कि 'ng-if =" ... "काम करता है जब परीक्षण की स्थिति कोणीय के दायरे में होती है इस प्रकार, आप इसे बदलने की कोशिश कर सकते हैं और अपनी इच्छित इच्छा (यानी चौड़ाई बनाम थ्रेसहोल्ड) को सक्षम करने के लिए '$ स्कोप' वैरिएबल अपडेट किया जा रहा है। – FDavidov

+0

हाय डेविड, आपकी टिप्पणी के लिए धन्यवाद। मैं केवल नया हूं कोणीय/कोणीय 2, लेकिन मुझे लगता है कि एनजी-अगर और $ स्कोप केवल अंगुलर में उपयोग किया जाता है और इसे कोणीय 2 में बदल दिया गया है। नीचे की रेखा में लिंक यदि आप रुचि रखते हैं तो वह मेरे उद्देश्य को बताता है। चीयर्स –

+0

ओउप्स !!! "2" मिस गया। माफ़ कीजिये। मैं समय के लिए कोणीय के साथ रहूँगा :-)। – FDavidov

उत्तर

50

आपकी समस्या का समाधान करने के लिए, आप बस हो जाते हैं और प्रत्येक का आकार परिवर्तन घटना के बाद एक घटक संपत्ति में div के आकार की दुकान, और टेम्पलेट में है कि संपत्ति का उपयोग करने की जरूरत है। इस तरह, मूल्य निरंतर रहेगा जब परिवर्तन का दूसरा दौर देव मोड में चलता है।

मैं भी अपने टेम्पलेट (window:resize) जोड़ने के बजाय @HostListener का उपयोग करें। div का संदर्भ प्राप्त करने के लिए हम @ViewChild का उपयोग करेंगे। और प्रारंभिक मान सेट करने के लिए हम जीवन चक्र हुक ngAfterViewInit() का उपयोग करेंगे।

import {Component, ViewChild, HostListener} from '@angular/core'; 

@Component({ 
    selector: 'my-app', 
    template: `<div #widgetParentDiv class="Content"> 
    <p>Sample widget</p> 
    <table><tr> 
     <td>Value1</td> 
     <td *ngIf="divWidth > 350">Value2</td> 
     <td *ngIf="divWidth > 700">Value3</td> 
     </tr> 
    </table>`, 
}) 
export class AppComponent { 
    divWidth = 0; 
    @ViewChild('widgetParentDiv') parentDiv:ElementRef; 
    @HostListener('window:resize') onResize() { 
    // guard against resize before view is rendered 
    if(this.parentDiv) { 
     this.divWidth = this.parentDiv.nativeElement.clientWidth; 
    } 
    } 
    ngAfterViewInit() { 
    this.divWidth = this.parentDiv.nativeElement.clientWidth; 
    } 
} 

बहुत बुरा है कि काम नहीं करता। हम के बाद यह जांच की गई

अभिव्यक्ति बदल गया है मिलता है। पिछला मान: 'झूठा'। वर्तमान मूल्य: 'सत्य'।

त्रुटि हमारे NgIf भाव के बारे में शिकायत कर रहा है - पहली बार इसे चलाता divWidth 0 है, तो ngAfterViewInit() रन है और 0 के अलावा कुछ करने के लिए मूल्य बदल जाता है, तो परिवर्तन का पता लगाने रन के 2 दौर (देव में मोड)। शुक्र है, वहाँ एक आसान/जाना जाता समाधान है, और यह एक बार की ही मुद्दा है, नहीं ओपी में की तरह एक सतत मुद्दा है:

ngAfterViewInit() { 
    // wait a tick to avoid one-time devMode 
    // unidirectional-data-flow-violation error 
    setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth); 
    } 

ध्यान दें कि यह तकनीक, एक टिक इंतजार कर के यहाँ से प्रलेखित है: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child

अक्सर, ngAfterViewInit() और ngAfterViewChecked() में हमें setTimeout() चाल को नियोजित करने की आवश्यकता होगी क्योंकि घटक के दृश्य के बाद इन विधियों को बुलाया जाता है।

यहां एक काम कर रहा है plunker


हम इसे बेहतर बना सकते हैं। मुझे लगता है कि हमें आकार बदलने की घटनाओं को थ्रॉटल करना चाहिए जैसे कि कोणीय परिवर्तन का पता लगाना, हर 100-250 एमएमएस कहता है, बल्कि हर बार एक आकार बदलने का आयोजन होता है। जब उपयोगकर्ता विंडो का आकार बदल रहा है, तो ऐप को आलसी होने से रोकना चाहिए, क्योंकि अभी, प्रत्येक आकार बदलने की घटना में परिवर्तन का पता लगाने का कारण बनता है (देव मोड में दो बार)।

ngDoCheck() { 
    console.log('change detection'); 
} 

observables कर सकते हैं आसानी से थ्रॉटल घटनाओं, इसलिए बजाय @HostListener का उपयोग कर आकार परिवर्तन घटना के लिए बाध्य करने के लिए की, हम एक नमूदार पैदा हो जाएगी:

Observable.fromEvent(window, 'resize') 
    .throttleTime(200) 
    .subscribe(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth); 
आप पिछले plunker के लिए निम्न विधि जोड़कर इस की पुष्टि कर सकते

यह काम करता है, लेकिन ... इसके साथ प्रयोग करते समय, मैंने कुछ बहुत ही रोचक खोज की ... भले ही हम आकार बदलने की घटना को थ्रॉटल करते हैं, फिर भी एंजुलर चेंज डिटेक्शन तब भी चलता है जब एक आकार बदलने का आयोजन होता है। यानी, थ्रॉटलिंग इस बात को प्रभावित नहीं करती है कि पहचान कितनी बार बदलती है। (टोबीस बॉश ने इसकी पुष्टि की: https://github.com/angular/angular/issues/1773#issuecomment-102078250।)

यदि घटना थ्रॉटल समय से गुज़रती है तो मैं केवल रन को बदलने के लिए बदलना चाहता हूं। और मुझे इस घटक पर चलाने के लिए केवल परिवर्तन पहचान की आवश्यकता है।

constructor(private ngzone: NgZone, private cdref: ChangeDetectorRef) {} 
ngAfterViewInit() { 
    // set initial value, but wait a tick to avoid one-time devMode 
    // unidirectional-data-flow-violation error 
    setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth); 
    this.ngzone.runOutsideAngular(() => 
    Observable.fromEvent(window, 'resize') 
     .throttleTime(200) 
     .subscribe(_ => { 
      this.divWidth = this.parentDiv.nativeElement.clientWidth; 
      this.cdref.detectChanges(); 
     }) 
); 
} 

यहाँ एक काम कर plunker है: समाधान कोणीय क्षेत्र के बाहर नमूदार बनाने के लिए है, तो मैन्युअल रूप से सदस्यता कॉलबैक अंदर परिवर्तन का पता लगाने कहते हैं।

प्लंकर में मैंने counter जोड़ा है कि मैं जीवन चक्र हुक ngDoCheck() का उपयोग करके प्रत्येक परिवर्तन पहचान चक्र को बढ़ाता हूं। आप देख सकते हैं कि इस विधि को – नहीं कहा जा रहा है काउंटर मान आकार बदलने पर ईवेंट नहीं बदलता है।

detectChanges() इस घटक और उसके बच्चों पर परिवर्तन का पता चलाएगा। यदि आप रूट घटक से परिवर्तन पहचान को चलाएंगे (यानी, एक पूर्ण परिवर्तन पहचान जांच चलाएं) तो इसके बजाय ApplicationRef.tick() का उपयोग करें (यह प्लंकर में टिप्पणी की गई है)। ध्यान दें कि tick() कहने के लिए ngDoCheck() का कारण बन जाएगा।


यह एक अच्छा सवाल है।मैंने अलग-अलग समाधानों की कोशिश करने में बहुत समय बिताया और मैंने बहुत कुछ सीखा। इस सवाल को पोस्ट करने के लिए धन्यवाद।

+0

मार्क, आपके विस्तृत उत्तर के लिए बहुत बहुत धन्यवाद। मैं इस पर हफ्तों के लिए अटक गया हूं और मेरी परियोजना में वापस आने की उम्मीद कर रहा हूं :) –

+0

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

+0

मार्क - एफवाईआई - मुझे अपने कोड में अपने प्लंकर को विलय करते समय 'ElementRef' आयात करना पड़ा था। कोई विचार है कि आपको अपने प्लंक में क्यों जरूरत नहीं है? ** दूसरा **, मुझे एक त्रुटि मिल रही है ~ प्रकार का तर्क '(_: कोई भी) => सदस्यता' 'this.ngzone.runOutsideAngular पर'() => any '। के पैरामीटर के लिए असाइन करने योग्य नहीं है। (_ => Observable.fromEvent (विंडो, 'आकार बदलें')। किसी भी विचार को इसे कैसे ठीक किया जाए? –

5

अन्य तरीका है कि मैं इस को हल करने के लिए इस्तेमाल किया:

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


@Component({ 
    selector: 'your-seelctor', 
    template: 'your-template', 
}) 

export class YourComponent{ 

    constructor(public cdRef:ChangeDetectorRef) { } 

    ngAfterViewInit() { 
    this.cdRef.detectChanges(); 
    } 
} 
संबंधित मुद्दे