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

जेराल्ड मोनाको
जेरल्ड मोनाको

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

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

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

मैन्युअल DOM में हेर-फेर करने से बचना

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

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

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

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

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

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

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

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

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

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

इसके बजाय, आपको हर दूसरे कॉम्पोनेंट को ब्लैक बॉक्स के तौर पर देखना चाहिए. यह तय करें कि कब और कहां दूसरे कॉम्पोनेंट (एक ही ऐप्लिकेशन या लाइब्रेरी में भी) को आपके कॉम्पोनेंट के व्यवहार या लुक के साथ इंटरैक्ट या कस्टमाइज़ करना पड़ सकता है. इसके बाद, ऐसा करने का सुरक्षित और पुराना तरीका दिखाएं. किसी सबट्री के लिए एपीआई उपलब्ध कराने के लिए, हैरारकी डिपेंडेंसी इंजेक्शन जैसी सुविधाओं का इस्तेमाल करें. ऐसा तब करें, जब आसान @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 में हेर-फेर करने की सुविधा रोकें

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

सिर्फ़-ब्राउज़र के लिए JavaScript चलाएं

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

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

पसंद के मुताबिक लेआउट बनाएं

कभी-कभी आपको कुछ ऐसे लेआउट करने के लिए DOM को पढ़ने या लिखने की ज़रूरत पड़ सकती है जो आपके टारगेट ब्राउज़र पर अभी तक काम नहीं करते, जैसे कि टूलटिप को पोज़िशन करना. इसके लिए afterRender एक बेहतरीन विकल्प है, क्योंकि आप पक्का कर सकते हैं कि DOM एक ही स्थिति में है. 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, ऐप्लिकेशन में मौजूद हर दूसरे कॉम्पोनेंट में DOM हेर-फेर को असरदार तरीके से बैच कर सकता है. इससे, परफ़ॉर्मेंस पर कम से कम असर पड़ता है.

नतीजा

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