NgFor Angular2 में पाइप के साथ डेटा अद्यतन नहीं करता है




angular2-template angular2-directives (4)

इस परिदृश्य में, मैं ngFor साथ देखने के लिए छात्रों (सरणी) की एक सूची प्रदर्शित कर रहा हूं:

<li *ngFor="#student of students">{{student.name}}</li>

यह आश्चर्यजनक है कि जब भी मैं सूची में अन्य छात्र जोड़ता हूं तो यह अपडेट होता है।

हालांकि, जब मैं इसे छात्र नाम से filter करने के लिए एक pipe देता हूं,

<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>

जब तक मैं फ़िल्टरिंग छात्र नाम फ़ील्ड में कुछ टाइप नहीं करता तब तक यह सूची अपडेट नहीं करता है।

यहां plnkr का एक लिंक है।

Hello_world.html

<h1>Students:</h1>
<label for="newStudentName"></label>
<input type="text" name="newStudentName" placeholder="newStudentName" #newStudentElem>
<button (click)="addNewStudent(newStudentElem.value)">Add New Student</button>
<br>
<input type="text" placeholder="Search" #queryElem (keyup)="0">
<ul>
    <li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>
</ul>

sort_by_name_pipe.ts

import {Pipe} from 'angular2/core';

@Pipe({
    name: 'sortByName'
})
export class SortByNamePipe {

    transform(value, [queryString]) {
        // console.log(value, queryString);
        return value.filter((student) => new RegExp(queryString).test(student.name))
        // return value;
    }
}

कोणीय दस्तावेज से

शुद्ध और अशुद्ध पाइप

पाइप की दो श्रेणियां हैं: शुद्ध और अशुद्ध। पाइप डिफ़ॉल्ट रूप से शुद्ध हैं। अब तक जो पाइप आपने देखा है वह शुद्ध रहा है। आप अपने शुद्ध झंडे को गलत पर सेट करके एक पाइप अशुद्ध बनाते हैं। आप फ्लाइंग हेरोस पाइप इस तरह अशुद्ध कर सकते हैं:

@Pipe({ name: 'flyingHeroesImpure', pure: false })

ऐसा करने से पहले, एक शुद्ध पाइप से शुरू, शुद्ध और अशुद्ध के बीच का अंतर समझें।

शुद्ध पाइप कोणीय केवल शुद्ध पाइप निष्पादित करता है जब यह इनपुट मान में शुद्ध परिवर्तन का पता लगाता है। शुद्ध परिवर्तन या तो एक आदिम इनपुट मान (स्ट्रिंग, संख्या, बूलियन, प्रतीक) या एक बदले ऑब्जेक्ट संदर्भ (दिनांक, ऐरे, फ़ंक्शन, ऑब्जेक्ट) में परिवर्तन होता है।

कोणीय (समग्र) वस्तुओं के भीतर परिवर्तनों को अनदेखा करता है। यदि आप इनपुट माह बदलते हैं, इनपुट एरे में जोड़ते हैं, या इनपुट ऑब्जेक्ट प्रॉपर्टी अपडेट करते हैं तो यह शुद्ध पाइप नहीं कॉल करेगा।

यह प्रतिबंधक प्रतीत हो सकता है लेकिन यह भी तेज़ है। एक ऑब्जेक्ट रेफरेंस चेक अंतर के लिए गहरी जांच से तेज़ी से तेज़ है-इसलिए कोणीय जल्दी से निर्धारित कर सकता है कि क्या यह पाइप निष्पादन और दृश्य अपडेट दोनों को छोड़ सकता है।

इस कारण से, जब आप परिवर्तन पहचान रणनीति के साथ रह सकते हैं तो शुद्ध पाइप बेहतर होता है। जब आप नहीं कर सकते, तो आप अशुद्ध पाइप का उपयोग कर सकते हैं।


जैसा कि एरिक मार्टिनेज ने टिप्पणियों में बताया, pure: false जोड़ना pure: false अपने Pipe सजावट और changeDetection: ChangeDetectionStrategy.OnPush अपने Component सजावट में changeDetection: ChangeDetectionStrategy.OnPush अपनी समस्या को ठीक करेगा। यहां एक काम करने वाला प्लंकर है। ChangeDetectionStrategy.Always बदल रहा है। ChangeDetectionStrategy.Always , काम करता है। यहाँ पर क्यों।

पाइप पर कोणीय 2 गाइड के अनुसार :

पाइप डिफ़ॉल्ट रूप से स्टेटलेस हैं। हमें @Pipe सजावट की pure संपत्ति को false सेट करके एक पाइप घोषित करना होगा। यह सेटिंग प्रत्येक चक्र को इस पाइप के आउटपुट की जांच करने के लिए कोणीय के परिवर्तन पहचान प्रणाली को बताती है, चाहे उसका इनपुट बदल गया हो या नहीं।

ChangeDetectionStrategy , डिफ़ॉल्ट रूप से, सभी बाइंडिंग प्रत्येक चक्र की जांच की जाती हैं। जब pure: false पाइप जोड़ा जाता है, तो मेरा मानना ​​है कि प्रदर्शन कारणों से CheckAlways से CheckAlways में परिवर्तन पहचान विधि बदल जाती है। OnPush साथ, घटक के लिए बाइंडिंग केवल तभी जांच की जाती है जब कोई इनपुट प्रॉपर्टी बदलती है या जब कोई ईवेंट ट्रिगर होता है। परिवर्तन डिटेक्टरों के बारे में अधिक जानकारी के लिए, angular2 का एक महत्वपूर्ण हिस्सा, निम्न लिंक देखें:


डेमो प्लंकर

आपको ChangeDetectionStrategy को बदलने की आवश्यकता नहीं है। एक राज्यव्यापी पाइप को कार्यान्वित करना सबकुछ काम करने के लिए पर्याप्त है।

यह एक राज्यव्यापी पाइप है (कोई अन्य परिवर्तन नहीं किए गए थे):

@Pipe({
  name: 'sortByName',
  pure: false
})
export class SortByNamePipe {
  tmp = [];
  transform (value, [queryString]) {
    this.tmp.length = 0;
    // console.log(value, queryString);
    var arr = value.filter((student)=>new RegExp(queryString).test(student.name));
    for (var i =0; i < arr.length; ++i) {
        this.tmp.push(arr[i]);
     }

    return this.tmp;
  }
}

समस्या और संभावित समाधानों को पूरी तरह से समझने के लिए, हमें पाइप और घटकों के लिए कोणीय परिवर्तन का पता लगाने की आवश्यकता है।

पाइप चेंज डिटेक्शन

स्टेटलेस / शुद्ध पाइप्स

डिफ़ॉल्ट रूप से, पाइप स्टेटलेस / शुद्ध होते हैं। स्टेटलेस / शुद्ध पाइप बस इनपुट डेटा को आउटपुट डेटा में बदल देते हैं। उन्हें कुछ भी याद नहीं है, इसलिए उनके पास कोई गुण नहीं है - बस एक transform() विधि। कोणीय इसलिए स्टेटलेस / शुद्ध पाइप के उपचार को अनुकूलित कर सकते हैं: यदि उनके इनपुट नहीं बदलते हैं, तो पाइप को एक परिवर्तन पहचान चक्र के दौरान निष्पादित करने की आवश्यकता नहीं होती है। एक पाइप के लिए {{power | exponentialStrength: factor}} {{power | exponentialStrength: factor}} power {{power | exponentialStrength: factor}} , power और factor इनपुट हैं।

इस प्रश्न के लिए, "#student of students | sortByName:queryElem.value" , students और queryElem.value इनपुट हैं, और पाइप sortByName स्टेटलेस / शुद्ध है। students एक सरणी (संदर्भ) है।

  • जब कोई छात्र जोड़ा जाता है, तो सरणी संदर्भ नहीं बदलता है - students नहीं बदलते हैं - इसलिए स्टेटलेस / शुद्ध पाइप निष्पादित नहीं होता है।
  • जब फ़िल्टर इनपुट में कुछ टाइप किया जाता है, queryElem.value बदलता है, इसलिए स्टेटलेस / शुद्ध पाइप निष्पादित होता है।

सरणी समस्या को ठीक करने का एक तरीका है जब भी कोई छात्र जोड़ा जाता है तो सरणी संदर्भ को बदलना है - यानी, जब भी कोई छात्र जोड़ा जाता है तो एक नई सरणी बनाएं। हम इसे concat() साथ कर सकते हैं:

this.students = this.students.concat([{name: studentName}]);

यद्यपि यह काम करता है, हमारी addNewStudent() विधि को एक निश्चित तरीके से लागू नहीं किया जाना चाहिए क्योंकि हम एक पाइप का उपयोग कर रहे हैं। हम अपने सरणी में जोड़ने के लिए push() का उपयोग करना चाहते हैं।

राज्यिक पाइप्स

राज्यिक पाइपों में राज्य होता है - वे आम तौर पर गुण होते हैं, न केवल एक transform() विधि। उन्हें मूल्यांकन करने की आवश्यकता हो सकती है भले ही उनके इनपुट नहीं बदला हो। जब हम निर्दिष्ट करते हैं कि एक पाइप राज्यव्यापी / गैर-शुद्ध है - pure: false - फिर जब भी कोणीय परिवर्तन पहचान प्रणाली परिवर्तन के लिए एक घटक की जांच करती है और वह घटक एक राज्यव्यापी पाइप का उपयोग करता है, तो यह पाइप के आउटपुट की जांच करेगा, चाहे उसका इनपुट बदल गया हो या नहीं।

ऐसा लगता है कि हम क्या चाहते हैं, भले ही यह कम कुशल हो, क्योंकि हम चाहते हैं कि पाइप निष्पादित हो, भले ही students संदर्भ नहीं बदला गया हो। अगर हम बस पाइप को स्टेटफुल करते हैं, तो हमें एक त्रुटि मिलती है:

EXCEPTION: Expression 'students | sortByName:queryElem.value  in [email protected]:6' 
has changed after it was checked. Previous value: '[object Object],[object Object]'. 
Current value: '[object Object],[object Object]' in [students | sortByName:queryElem.value

@ ड्रूमोर के उत्तर के मुताबिक, "यह त्रुटि केवल देव मोड में होती है (जिसे बीटा -0 के रूप में डिफ़ॉल्ट रूप से सक्षम किया जाता है)। यदि आप ऐप बूटस्ट्रैप करते समय enableProdMode() कॉल करते हैं, तो त्रुटि फेंक नहीं जाएगी।" ApplicationRef.tick() राज्य के लिए दस्तावेज़ :

विकास मोड में, टिक () यह सुनिश्चित करने के लिए एक दूसरा परिवर्तन पहचान चक्र भी करता है कि कोई और परिवर्तन नहीं पता चला है। यदि इस दूसरे चक्र के दौरान अतिरिक्त परिवर्तन उठाए जाते हैं, तो ऐप में बाइंडिंग के दुष्प्रभाव होते हैं जिन्हें एकल परिवर्तन पहचान पास में हल नहीं किया जा सकता है। इस मामले में, कोणीय एक त्रुटि फेंकता है, क्योंकि एक कोणीय अनुप्रयोग में केवल एक परिवर्तन पहचान पास हो सकता है जिसके दौरान सभी परिवर्तन पहचान पूरी होनी चाहिए।

हमारे परिदृश्य में मेरा मानना ​​है कि त्रुटि फर्जी / भ्रामक है। हमारे पास एक राज्यव्यापी पाइप है, और जब भी इसे बुलाया जाता है तो आउटपुट बदल सकता है - इसका दुष्प्रभाव हो सकता है और यह ठीक है। पाइप के बाद NgFor का मूल्यांकन किया जाता है, इसलिए इसे ठीक काम करना चाहिए।

हालांकि, हम वास्तव में इस त्रुटि को फेंकने के साथ विकसित नहीं कर सकते हैं, इसलिए एक वर्कअराउंड पाइप कार्यान्वयन के लिए एक सरणी संपत्ति (यानी, राज्य) जोड़ना है और हमेशा उस सरणी को वापस करना है। इस समाधान के लिए @ पिक्सेलबिट्स का उत्तर देखें।

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

घटक परिवर्तन पहचान

डिफ़ॉल्ट रूप से, प्रत्येक ब्राउज़र ईवेंट पर, कोणीय परिवर्तन का पता लगाने के लिए यह देखने के लिए प्रत्येक घटक के माध्यम से जाता है - इनपुट और टेम्पलेट्स (और शायद अन्य सामान?) की जांच की जाती है।

यदि हम जानते हैं कि एक घटक केवल इसके इनपुट गुणों (और टेम्पलेट घटनाओं) पर निर्भर करता है, और इनपुट गुण अपरिवर्तनीय हैं, तो हम अधिक कुशलतापूर्वक onPush परिवर्तन पहचान रणनीति का उपयोग कर सकते हैं। इस रणनीति के साथ, प्रत्येक ब्राउज़र ईवेंट की जांच करने के बजाय, एक घटक केवल तब चेक किया जाता है जब इनपुट बदलते हैं और जब टेम्पलेट ईवेंट ट्रिगर होते हैं। और जाहिर है, हमें यह Expression ... has changed after it was checked नहीं मिलती है Expression ... has changed after it was checked इस सेटिंग के साथ त्रुटि की Expression ... has changed after it was checked । ऐसा इसलिए है क्योंकि एक onPush घटक तब तक चेक नहीं किया जाता है जब तक कि यह "चिह्नित" ( ChangeDetectorRef.markForCheck() ) दोबारा न हो। तो टेम्पलेट बाइंडिंग और स्टेटफुल पाइप आउटपुट केवल एक बार निष्पादित / मूल्यांकन किए जाते हैं। स्टेटलेस / शुद्ध पाइप अभी भी निष्पादित नहीं किए जाते हैं जब तक कि उनके इनपुट में परिवर्तन न हो। तो हमें अभी भी एक राज्यव्यापी पाइप की जरूरत है।

यह समाधान @EricMartinez सुझाव दिया गया है: onPush परिवर्तन पहचान के साथ onPush पाइप। इस समाधान के लिए @ caffinatedmonkey का जवाब देखें।

ध्यान दें कि इस समाधान के साथ transform() विधि को हर बार एक ही सरणी को वापस करने की आवश्यकता नहीं होती है। मुझे लगता है कि थोड़ा अजीब हालांकि: कोई राज्य के साथ एक राज्यव्यापी पाइप। इसके बारे में कुछ और सोच रहा है ... राज्यव्यापी पाइप शायद हमेशा एक ही सरणी वापस करनी चाहिए। अन्यथा इसका उपयोग केवल देव मोड में onPush घटकों के साथ किया जा सकता है।

तो इसके बाद, मुझे लगता है कि मुझे @ एरिक और @ पिक्सेलबिट के उत्तरों का संयोजन पसंद है: स्टेटफुल पाइप जो समान सरणी संदर्भ देता है, अगर घटक इसे अनुमति देता है तो onPush चेंज डिटेक्शन के साथ। चूंकि राज्यव्यापी पाइप एक ही सरणी संदर्भ देता है, इसलिए पाइप अभी भी उन घटकों के साथ उपयोग किया जा सकता है जो onPush साथ कॉन्फ़िगर नहीं हैं।

Plunker

यह शायद एक कोणीय 2 मुहावरे बन जाएगा: यदि कोई सरणी पाइप को खिला रही है, और सरणी बदल सकती है (सरणी में आइटम, सरणी संदर्भ नहीं है), तो हमें एक राज्यव्यापी पाइप का उपयोग करने की आवश्यकता है।