ControlValueAccessor और कोणीय में संतुष्ट

क्या आपने कभी सोचा है कि उपयोगकर्ता डेटा के माध्यम से कोणीय रूपों और HTML तत्वों का संयोजन कैसे करते हैं?


शुरुआत से ही हमने ControlValueAccessor उपयोग किया, एक विशेष इंटरफ़ेस जिसमें केवल 4 विधियाँ शामिल ControlValueAccessor :


 interface ControlValueAccessor { writeValue(value: any): void registerOnChange(fn: (value: any) => void): void registerOnTouched(fn: () => void): void setDisabledState(isDisabled: boolean)?: void } 

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


कोणीय, contenteditable साथ रूपों के उपयोग का समर्थन नहीं करता है, इसलिए आपको इसे स्वयं लिखना होगा।



ControlValueAccessor


जो निर्देश हम लिखते हैं, वह बिल्ट-इन एक्सेसर्स के समान काम करेगा - यह सामग्री के contenteditable होने पर प्रतिक्रिया देगा। टेम्पलेट और प्रतिक्रियाशील रूपों के लिए निर्भरता इंजेक्शन के माध्यम से इसे प्राप्त करने के लिए, यह एक अंतर्निहित InjectionToken प्रदान करने के लिए पर्याप्त है:


 @Directive({ selector: '[contenteditable][formControlName],' + '[contenteditable][formControl],' + '[contenteditable][ngModel]', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ContenteditableValueAccessor), multi: true, }, ], }) export class ContenteditableValueAccessor implements ControlValueAccessor { // ... } 

ControlValueAccessor इंटरफ़ेस के लिए 3 विधियों की आवश्यकता है और इसमें 1 वैकल्पिक विधि है:


  • registerOnChange - एक फ़ंक्शन आरंभीकरण के दौरान इस पद्धति में आएगा। इसे नए मूल्य के साथ कहा जाता है जब उपयोगकर्ता ने हमारे घटक में कुछ दर्ज किया है ताकि नया डेटा नियंत्रण में दर्ज हो।
  • registerOnTouched - एक फ़ंक्शन प्रारंभ के दौरान इस पद्धति में आएगा। इसे तब कहा जाना चाहिए जब नियंत्रण को touched लिए उपयोगकर्ता ने हमारे घटक को छोड़ दिया। यह सत्यापन के लिए उपयोग किया जाता है।
  • writeValue - writeValue - इस विधि को हमारे घटक को मान पास करने के लिए नियंत्रण द्वारा बुलाया जाएगा। इसका उपयोग तब किया जाता है जब मान बाहर के कोड के माध्यम से बदल जाता है ( setValue या उस चर को ngModel जो ngModel से जुड़ा हुआ ngModel ), साथ ही प्रारंभिक मूल्य सेट करने के लिए।
    यह ध्यान देने योग्य है कि, प्रतिक्रियाशील रूपों के विपरीत, ngModel व्यवहार करता है - विशेष रूप से, इसमें प्रारंभिक मूल्य एक देरी के साथ शुरू होता है, और writeValue पद्धति " writeValue " दो बार, null साथ पहली बार:
    https://github.com/angular/angular/issues/14988
  • setDisabledState (वैकल्पिक) - disabled अवस्था में परिवर्तन होने पर इस विधि को नियंत्रण द्वारा बुलाया जाएगा। यद्यपि यह विधि वैकल्पिक है, लेकिन आपके घटक में इसका जवाब देना बेहतर है।

इंटरफ़ेस कार्यान्वयन


DOM तत्व के साथ काम करने के लिए, हमें Renderer2 आवश्यकता है और, वास्तव में, तत्व स्वयं, इसलिए उन्हें कंस्ट्रक्टर में जोड़ें:


 constructor( @Inject(ElementRef) private readonly elementRef: ElementRef, @Inject(Renderer2) private readonly renderer: Renderer2, ) {} 

हम उन तरीकों को सहेजते हैं जो नियंत्रण हमें कक्षा के निजी क्षेत्रों में पास करेंगे:


 private onTouched = () => {}; private onChange: (value: string) => void = () => {}; registerOnChange(onChange: (value: string) => void) { this.onChange = onChange; } registerOnTouched(onTouched: () => void) { this.onTouched = onTouched; } 

contenteditable घटक के लिए disabled अवस्था संपादन मोड को निष्क्रिय करने के बराबर है - contenteditable="false" । बाहर से मान सेट करना तत्व के innerHTML डोम की जगह के बराबर है, और उपयोगकर्ता द्वारा मूल्य को अपडेट करना और घटक को छोड़ कर संबंधित घटनाओं की सदस्यता लेकर निगरानी की जा सकती है:


 @HostListener('input') onInput() { this.onChange(this.elementRef.nativeElement.innerHTML); } @HostListener('blur') onBlur() { this.onTouched(); } setDisabledState(disabled: boolean) { this.renderer.setAttribute( this.elementRef.nativeElement, 'contenteditable', String(!disabled), ); } writeValue(value: string) { this.renderer.setProperty( this.elementRef.nativeElement, 'innerHTML', value, ); } 

वह, वास्तव में, सब है। यह कोणीय रूपों और contenteditable तत्वों के काम के मूल कार्यान्वयन के लिए पर्याप्त है।


इंटरनेट एक्सप्लोरर


हालाँकि, एक युगल है लेकिन


सबसे पहले, फॉर्म का खाली प्रारंभिक मान null , और IE11 में writeValue बाद, हम टेम्पलेट में null देखेंगे। कार्य को सही ढंग से लागू करने के लिए, हमें मूल्य को सामान्य करने की आवश्यकता है:


 writeValue(value: string | null) { this.renderer.setProperty( this.elementRef.nativeElement, 'innerHTML', ContenteditableValueAccessor.processValue(value), ); } private static processValue(value: string | null): string { const processed = value || ''; return processed.trim() === '<br>' ? '' : processed; } 

यहां हम निम्नलिखित स्थिति को भी संभालेंगे। कल्पना करें कि किसी तत्व की सामग्री में HTML टैग थे। यदि हम बस सब कुछ का चयन करते हैं और इसे हटाते हैं, तो यह अंदर खाली नहीं होगा - एक अकेला <br> टैग वहां डाला जाएगा। खाली मान के साथ नियंत्रण को रोकना नहीं करने के लिए, हम इसे एक रिक्त स्ट्रिंग के रूप में मानेंगे।


दूसरी बात, इंटरनेट एक्सप्लोरर contenteditable तत्वों के लिए input घटनाओं का समर्थन नहीं करता है। हमें MutationObserver का उपयोग करके फॉलबैक लागू करना होगा:


 private readonly observer = new MutationObserver(() => { this.onChange( ContenteditableValueAccessor.processValue( this.elementRef.nativeElement.innerHTML, ), ); }); ngAfterViewInit() { this.observer.observe(this.elementRef.nativeElement, { characterData: true, childList: true, subtree: true, }); } ngOnDestroy() { this.observer.disconnect(); } 

हम किसी विशिष्ट ब्राउज़र पर सत्यापन लागू नहीं करेंगे। इसके बजाय, हम तुरंत पहली input घटना पर MutationObserver को अक्षम करते हैं:


 @HostListener('input') onInput() { this.observer.disconnect(); // ... } 

अब हमारा घटक IE11 में काम करता है और हम खुद से संतुष्ट हैं!


इंटरनेट एक्सप्लोरर! ಠ (ಠ 益 ಠ ಠ)


दुर्भाग्य से, IE11 को अभी पीछे नहीं छोड़ा गया है। जाहिरा तौर पर, MutationObserver के काम में एक बग है। यदि सामग्री के तत्व के अंदर टैग हैं, उदाहरण के लिए, some <b>text</b> , तो जब पाठ हटा रहा है, जो पूरे टैग (इस उदाहरण में शब्द text ) को हटाने की आवश्यकता होगी, पर्यवेक्षक के कॉलबैक को वास्तविक से पहले बुलाया जाएगा DOM में बदलाव!



दुर्भाग्य से, हमारे पास हार स्वीकार करने और setTimeout उपयोग करने के अलावा कोई विकल्प नहीं है:


 private readonly observer = new MutationObserver(() => { setTimeout(() => { this.onChange( ContenteditableValueAccessor.processValue( this.elementRef.nativeElement.innerHTML, ), ); }); }); 

निष्कर्ष


बशर्ते कि कोणीय को इंटरनेट एक्सप्लोरर के संस्करणों का समर्थन करना चाहिए 9, 10 और 11, यह स्पष्ट हो जाता है कि उन्होंने घर में contenteditable साथ काम क्यों लागू नहीं किया।


इसके अलावा, आपको यह याद रखना होगा कि HTML में दुर्भावनापूर्ण कोड हो सकता है - इसलिए, साहसपूर्वक अज्ञात सामग्री न लें और इसे नियंत्रण में paste करें, और उपयोगकर्ता इनपुट को paste और drop ईवेंट में जांचना आवश्यक है। इस लेख में वर्णित कोड कोणीय 4 और उच्चतर में काम करता है, जिसमें FormHooks भी FormHooks । यदि आप चाहें, तो आप Renderer उपयोग कर सकते हैं, यदि आप Renderer उपयोग करते हैं, तो Renderer 2 का नहीं। स्रोत कोड और npm पैकेज लिंक पर उपलब्ध हैं:


https://github.com/TinkoffCreditSystems/angular-contenteditable-accessor
https://www.npmjs.com/package/@tinkoff/angular-contenteditable-accessor


और यहाँ एक उदाहरण के साथ खेलते हैं:
https://stackblitz.com/edit/angular2-contenteditable-value-accessor

Source: https://habr.com/ru/post/hi443714/


All Articles