ऐंगुलर एसएसआर की मदद से, डीओएम को सुरक्षित तरीके से ऐक्सेस करना

Gerald Monaco
Gerald Monaco

पिछले साल, Angular ने हाइड्रेशन और डिफ़रेबल व्यू जैसी कई नई सुविधाएं हासिल की हैं. इससे डेवलपर को अपनी वेबसाइट की परफ़ॉर्मेंस की अहम जानकारी में सुधार करने और अपने असली उपयोगकर्ताओं को बेहतरीन अनुभव देने में मदद मिली. इस सुविधा पर काम करने वाली, सर्वर साइड रेंडरिंग से जुड़ी अतिरिक्त सुविधाओं पर भी रिसर्च चल रही है. इनमें स्ट्रीमिंग और आंशिक हाइड्रेशन जैसी सुविधाएं शामिल हैं.

माफ़ करें, एक पैटर्न ऐसा हो सकता है जो आपके ऐप्लिकेशन या लाइब्रेरी को इन सभी नई और आने वाली सुविधाओं का पूरा फ़ायदा उठाने से रोक सकता है: मौजूदा डीओएम स्ट्रक्चर में मैन्युअल तरीके से बदलाव करना. ऐंग्युलर के लिए यह ज़रूरी है कि सर्वर पर किसी कॉम्पोनेंट को क्रम में लगाए जाने के समय से लेकर डीओएम का स्ट्रक्चर तब तक एक जैसा बना रहे, जब तक कि वह ब्राउज़र पर हाइड्रेट न हो जाए. हाइड्रेशन से पहले, डीओएम में मैन्युअल तरीके से नोड जोड़ने, एक जगह से दूसरी जगह ले जाने या हटाने के लिए, ElementRef, Renderer2 या डीओएम एपीआई का इस्तेमाल करने पर, कुछ सुविधाएं अलग-अलग हो सकती हैं. इस वजह से, ये सुविधाएं काम नहीं कर पाती हैं.

हालांकि, सभी मैन्युअल डीओएम हेर-फेर और ऐक्सेस में समस्या नहीं होती और कभी-कभी यह ज़रूरी होता है. डीओएम का सुरक्षित तरीके से इस्तेमाल करने का सबसे अहम तरीका यह है कि आप इसकी ज़रूरत को कम से कम करें. साथ ही, जितना हो सके उतना देर तक इसका इस्तेमाल करें. नीचे दिए गए दिशा-निर्देशों में बताया गया है कि इसे कैसे पूरा किया जा सकता है. साथ ही, यह भी बताते हैं कि Angular कॉम्पोनेंट पूरी तरह से यूनिवर्सल और भविष्य में इस्तेमाल होने वाले, Angular के सभी कॉम्पोनेंट को कैसे बना सकते हैं, जो Angular की सभी नई और आने वाली सुविधाओं का पूरा फ़ायदा ले सकते हैं.

मैन्युअल डीओएम में बदलाव से बचें

मैन्युअल डीओएम हेर-फेर की वजह से होने वाली समस्याओं से बचने का सबसे अच्छा तरीका यह है कि जहां भी मुमकिन हो वहां इससे पूरी तरह से बचें. Angular में पहले से ऐसे एपीआई और पैटर्न मौजूद हैं जो डीओएम के ज़्यादातर पहलुओं में हेर-फेर कर सकते हैं: आपको सीधे डीओएम ऐक्सेस करने के बजाय उनका इस्तेमाल करना चाहिए.

कॉम्पोनेंट के अपने DOM एलिमेंट में बदलाव करें

कोई कॉम्पोनेंट या निर्देश लिखते समय, आपको किसी रैपर एलिमेंट को टारगेट करने या शुरुआती जानकारी देने के बजाय होस्ट एलिमेंट (यानी, वह डीओएम एलिमेंट जो कॉम्पोनेंट या निर्देश के सिलेक्टर से मिलता-जुलता है) में बदलाव करना पड़ सकता है. उदाहरण के लिए, क्लास, स्टाइल या एट्रिब्यूट जोड़ना. पहले से मौजूद डीओएम एलिमेंट में बदलाव करने के लिए, सिर्फ़ ElementRef तक पहुंचना आकर्षक लगता है. इसके बजाय, वैल्यू को किसी एक्सप्रेशन से बाइंड करने के लिए, होस्ट बाइंडिंग का इस्तेमाल करना चाहिए, ताकि डिक्लेरेटिव तरीके से वैल्यू बाइंड की जा सकें:

@Component({
  selector: 'my-component',
  template: `...`,
  host: {
    '[class.foo]': 'true'
  },
})
export class MyComponent {
  /* ... */
}

उदाहरण के लिए, एचटीएमएल में डेटा बाइंडिंग की तरह ही, एट्रिब्यूट और स्टाइल के साथ भी बाइंड किया जा सकता है. साथ ही, 'true' को किसी ऐसे अलग एक्सप्रेशन से बदला जा सकता है जिसका इस्तेमाल, Angular ज़रूरत के मुताबिक वैल्यू को अपने-आप जोड़ने या हटाने के लिए करेगा.

कुछ मामलों में, key को डाइनैमिक तरीके से कैलकुलेट करने की ज़रूरत होगी. आपके पास ऐसे सिग्नल या फ़ंक्शन से भी बाइंड करने का विकल्प होता है जो वैल्यू का सेट या मैप दिखाता है:

@Component({
  selector: 'my-component',
  template: `...`,
  host: {
    '[class.foo]': 'true',
    '[class]': 'classes()'
  },
})
export class MyComponent {
  size = signal('large');
  classes = computed(() => {
    return [`size-${this.size()}`];
  });
}

ज़्यादा कॉम्प्लेक्स ऐप्लिकेशन होने पर, ExpressionChangedAfterItHasBeenCheckedError से बचने के लिए, मैन्युअल डीओएम में बदलाव करने का मन कर सकता है. इसके बजाय, पिछले उदाहरण की तरह, वैल्यू को सिग्नल से बाइंड किया जा सकता है. यह काम ज़रूरत के मुताबिक किया जा सकता है. साथ ही, इसके लिए आपके पूरे कोड बेस में सिग्नल अपनाने की ज़रूरत नहीं होती.

टेंप्लेट के बाहर डीओएम एलिमेंट को बदलें

उन एलिमेंट को ऐक्सेस करने के लिए डीओएम का इस्तेमाल करना दिलचस्प होता है जिन्हें आम तौर पर ऐक्सेस नहीं किया जा सकता. उदाहरण के लिए, वे एलिमेंट जो अन्य पैरंट या चाइल्ड कॉम्पोनेंट से जुड़े होते हैं. हालांकि, इससे गड़बड़ी होने की आशंका होती है और इनकैप्सुलेशन का उल्लंघन होता है. इसकी वजह से, आने वाले समय में उन कॉम्पोनेंट को बदलना या अपग्रेड करना मुश्किल हो जाता है.

इसके बजाय, आपके कॉम्पोनेंट को हर दूसरे कॉम्पोनेंट को ब्लैक बॉक्स समझना चाहिए. इस बात का ध्यान रखें कि अन्य कॉम्पोनेंट (किसी एक ही ऐप्लिकेशन या लाइब्रेरी में भी) को कब और कहां, इंटरैक्ट करने की ज़रूरत पड़ सकती है या अपने कॉम्पोनेंट के काम करने के तरीके या लुक को पसंद के मुताबिक बनाना पड़ सकता है. इसके बाद, यह देखें कि कॉम्पोनेंट को सुरक्षित तरीके से दिखाना है या नहीं. जब आसान @Input और @Output प्रॉपर्टी काफ़ी न हों, तो सबट्री को एपीआई उपलब्ध कराने के लिए, हैरारिकल डिपेंडेंसी इंजेक्शन जैसी सुविधाओं का इस्तेमाल करें.

पहले, <body> या किसी अन्य होस्ट एलिमेंट के आखिर में कोई एलिमेंट जोड़कर, मॉडल डायलॉग या टूलटिप जैसी सुविधाओं को लागू करना और फिर कॉन्टेंट को वहां ले जाना या प्रोजेक्ट करना आम बात थी. हालांकि, इन दिनों इसके बजाय अपने टेंप्लेट में कोई आसान <dialog> एलिमेंट रेंडर किया जा सकता है:

@Component({
  selector: 'my-component',
  template: `<dialog #dialog>Hello World</dialog>`,
})
export class MyComponent {
  @ViewChild('dialog') dialogRef!: ElementRef;

  constructor() {
    afterNextRender(() => {
      this.dialogRef.nativeElement.showModal();
    });
  }
}

मैन्युअल डीओएम में बदलाव को रोकें

अपने डायरेक्ट DOM हेर-फेर को कम करने और जितना ज़्यादा से ज़्यादा ऐक्सेस हो सके उतना कम करने के लिए पिछले दिशा-निर्देशों का इस्तेमाल करने के बाद, आपके पास कुछ ऐसी चीज़ें बची हो सकती हैं जिन्हें छोड़ा नहीं जा सकता. ऐसे मामलों में, जितना हो सके उतना टालना ज़रूरी है. ऐसा करने के लिए, afterRender और afterNextRender कॉलबैक एक अच्छा तरीका है. ऐसा इसलिए, क्योंकि Angular किसी बदलाव की जांच करने और उन्हें डीओएम के लिए प्रतिबद्ध करने के बाद, सिर्फ़ ब्राउज़र पर चलता है.

सिर्फ़ ब्राउज़र पर JavaScript चलाएं

कुछ मामलों में आपके पास ऐसी लाइब्रेरी या एपीआई होगा जो सिर्फ़ ब्राउज़र में काम करता है. उदाहरण के लिए, चार्ट लाइब्रेरी, IntersectionObserver का कुछ इस्तेमाल वगैरह. ब्राउज़र पर चल रहा है या नहीं, यह देखने या सर्वर पर स्टबिंग की प्रोसेस बंद करने के बजाय, सिर्फ़ afterNextRender का इस्तेमाल किया जा सकता है:

@Component({
  /* ... */
})
export class MyComponent {
  @ViewChild('chart') chartRef: ElementRef;
  myChart: MyChart|null = null;
  
  constructor() {
    afterNextRender(() => {
      this.myChart = new MyChart(this.chartRef.nativeElement);
    });
  }
}

पसंद के मुताबिक लेआउट इस्तेमाल करें

कभी-कभी आपको कुछ ऐसे लेआउट करने के लिए डीओएम को पढ़ने या लिखने की ज़रूरत पड़ सकती है जो आपके टारगेट ब्राउज़र पर काम नहीं करते, जैसे कि टूलटिप को पोज़िशन करना. इसके लिए afterRender एक बेहतरीन विकल्प है, क्योंकि इस बात पर भरोसा किया जा सकता है कि डीओएम एक जैसा है. afterRender और afterNextRender, EarlyRead, Read या Write की phase वैल्यू स्वीकार करते हैं. लिखने के बाद डीओएम लेआउट को पढ़ने से ब्राउज़र, सिंक्रोनस रूप से लेआउट की दोबारा गणना करता है, जिससे परफ़ॉर्मेंस पर गंभीर रूप से असर पड़ सकता है (देखें: लेआउट थ्रैशिंग). इसलिए, यह ज़रूरी है कि अपने लॉजिक को सही चरणों में सावधानी से बांटा जाए.

उदाहरण के लिए, जो टूलटिप कॉम्पोनेंट पेज पर किसी दूसरे एलिमेंट के मुकाबले टूलटिप दिखाना चाहता है वह दो चरणों का इस्तेमाल कर सकता है. EarlyRead फ़ेज़ का इस्तेमाल, सबसे पहले एलिमेंट का साइज़ और पोज़िशन हासिल करने के लिए किया जाएगा:

afterRender(() => {
    targetRect = targetEl.getBoundingClientRect();
    tooltipRect = tooltipEl.getBoundingClientRect();
  }, { phase: AfterRenderPhase.EarlyRead },
);

इसके बाद, टूलटिप की जगह बदलने के लिए, Write फ़ेज़ में, पहले पढ़ी गई वैल्यू का इस्तेमाल किया जाएगा:

afterRender(() => {
    tooltipEl.style.setProperty('left', `${targetRect.left   targetRect.width / 2 - tooltipRect.width / 2}px`);
    tooltipEl.style.setProperty('top', `${targetRect.bottom - 4}px`);
  }, { phase: AfterRenderPhase.Write },
);

अपने लॉजिक को सही फ़ेज़ में बांटकर, Angular ऐप्लिकेशन में मौजूद सभी दूसरे कॉम्पोनेंट में डीओएम हेर-फेर को असरदार तरीके से बैच बनाने में मदद कर सकता है. इससे, परफ़ॉर्मेंस पर कम से कम असर पड़ता है.

नतीजा

Angular सर्वर साइड रेंडरिंग में कई नए और रोमांचक सुधार किए गए हैं. इसका मकसद, उपयोगकर्ताओं को बेहतरीन अनुभव देना आसान बनाना है. हम आशा करते हैं कि पिछली सलाह आपके ऐप्लिकेशन और लाइब्रेरी में उनका पूरा फ़ायदा लेने में आपकी मदद करने में मददगार साबित होंगी!