خلال العام الماضي، اكتسبت منصة Angular العديد من الميزات الجديدة، مثل تحويل صفحة ويب ثابتة إلى صفحة ديناميكية وطرق عرض يمكن تأجيلها لمساعدة المطوّرين على تحسين مؤشرات أداء الويب الأساسية وضمان تجربة رائعة للمستخدمين النهائيين. ويتم أيضًا إجراء أبحاث حول ميزات إضافية ذات صلة بالعرض من جهة الخادم تستند إلى هذه الوظيفة، مثل البث والاستفادة الجزئية من الماء.
هناك نمط واحد قد يمنع التطبيق أو المكتبة من الاستفادة بشكل كامل من جميع هذه الميزات الجديدة والقادمة، وهو: المعالجة اليدوية لبنية DOM الأساسية. يتطلب Angular أن تظل بنية DOM متسقة ابتداءً من وقت تنفيذ الخادم لتسلسل هرمي، حتى يتم ترطيبها على المتصفح. إنّ استخدام واجهات برمجة تطبيقات ElementRef
أو Renderer2
أو DOM لإضافة العُقد أو نقلها أو إزالتها يدويًا من نموذج العناصر في المستند (DOM) قبل أن تؤدي عملية نقل البيانات إلى حدوث تناقضات تمنع هذه الميزات من العمل.
مع ذلك، لا تشكّل كل أنواع عمليات التعامل مع نموذج العناصر في المستند (DOM) والوصول إليها مشاكل، وقد تكون ضرورية أحيانًا. يتمثل مفتاح استخدام نموذج العناصر في المستند (DOM) بأمان في تقليل حاجتك إليه قدر الإمكان، ثم تأجيل استخدامه لأطول فترة ممكنة. توضّح الإرشادات التالية كيفية تحقيق ذلك وتصميم مكونات Angular عالمية ومستدامة يمكن أن تستفيد بشكل كامل من جميع ميزات Angular الجديدة والقادمة.
تجنُّب التلاعب اليدوي في DOM
وليس من المستغرب أنّ الطريقة الأفضل لتجنّب المشاكل التي يسببها التلاعب اليدوي في نموذج العناصر في المستند (DOM) هي تجنّبها تمامًا كلما أمكن ذلك. يحتوي Angular على واجهات برمجة تطبيقات وأنماط مضمَّنة يمكنها معالجة معظم جوانب DOM: يُفضَّل استخدامها بدلاً من الوصول إلى DOM مباشرةً.
تغيير عنصر DOM الخاص بالمكوِّن
عند كتابة مكوّن أو توجيه، قد تحتاج إلى تعديل عنصر المضيف (أي عنصر DOM الذي يتطابق مع أداة اختيار المكوِّن أو التوجيه) لإضافة فئة أو نمط أو سمة، بدلاً من استهداف أو تقديم عنصر برنامج تضمين. إنه لأمر مغري الاكتفاء فقط بالوصول إلى ElementRef
لإجراء تغيير في عنصر DOM الأساسي. بدلاً من ذلك، عليك استخدام عمليات ربط المضيف لربط القيم بشكل صريح بتعبير آخر:
@Component({
selector: 'my-component',
template: `...`,
host: {
'[class.foo]': 'true'
},
})
export class MyComponent {
/* ... */
}
وكما هو الحال مع ربط البيانات في HTML، يمكنك أيضًا، على سبيل المثال، الربط بالسمات والأنماط وتغيير '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()}`];
});
}
في التطبيقات الأكثر تعقيدًا، قد يكون من المُغري تنفيذ معالجة DOM اليدوية لتجنب ExpressionChangedAfterItHasBeenCheckedError
. بدلاً من ذلك، يمكنك ربط القيمة بإشارة كما في المثال السابق. ويمكن إجراء ذلك حسب الحاجة، ولا يتطلب اعتماد إشارات على مستوى قاعدة الرموز البرمجية بالكامل.
تغيير عناصر DOM خارج نموذج
قد يغري محاولة استخدام نموذج كائن المستند (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
قيمة phase
بقيمة EarlyRead
أو Read
أو Write
. وتجبر قراءة تنسيق نموذج العناصر في المستند (DOM) بعد كتابته على إجبار المتصفّح على إعادة حساب التنسيق بشكل متزامن، ما قد يؤثر سلبًا في الأداء (يمكنك الاطّلاع على: تقسيم التنسيق). لذلك من المهم تقسيم منطقك بعناية إلى المراحل الصحيحة.
على سبيل المثال، من المحتمل أن يستخدم مكوّن التلميح الذي يريد عرض تلميح مرتبط بعنصر آخر على الصفحة مرحلتَين. وسيتم استخدام مرحلة 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 من جهة الخادم، وذلك بهدف تسهيل عملية تقديم تجربة رائعة للمستخدمين. نأمل أن تكون النصائح السابقة مفيدة في مساعدتك على الاستفادة إلى أقصى حد من هذه النصائح في التطبيقات والمكتبات.