تحميل عنصر كسول في الزاوي؟ ربما نحن نتحدث عن وحدات التحميل البطيئة باستخدام جهاز التوجيه الزاوي؟ لا ، نحن نتحدث عن المكونات. الإصدار الحالي من Angular يدعم فقط تحميل وحدة الكسول. لكن Ivy يمنح المطور فرصًا جديدة في العمل مع المكونات.

الحمل البطيء الذي استخدمناه حتى الآن: الطرق
التحميل الكسول هو آلية رائعة. في Angular ، يمكنك استخدام هذه الآلية دون الاضطرار إلى بذل أي جهد تقريبًا من خلال إعلان الطرق التي تدعم التحميل الكسول. فيما يلي مثال من
الوثائق الزاوية يوضح هذا:
const routes: Routes = [ { path: 'customer-list', loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule) } ];
بفضل الرمز أعلاه ، سيتم إنشاء جزء منفصل ل
customers.module
، وسيتم تحميله عندما يتنقل المستخدم
customer-list
مسار
customer-list
.
هذه طريقة لطيفة جدًا لتقليل حجم الحزمة الرئيسية للمشروع وتسريع التحميل الأولي للتطبيق.
ولكن على الرغم من ذلك ، ألا يكون من الجيد أن تكون قادرًا على التحكم في التحميل الكسول بشكل أكثر دقة؟ على سبيل المثال ، ماذا لو استطعنا تنظيم التحميل البطيء للمكونات الفردية؟
حتى الآن ، هذا لم يكن ممكنا. لكن كل ذلك تغير مع ظهور اللبلاب.
لبلاب ومفهوم "محلية"
الوحدات النمطية هي المفهوم الأساسي واللبنة الأساسية لكل تطبيق Angular. الوحدات تعلن مكونات ، توجيهات ، أنابيب ، خدمات.
لا يمكن أن يوجد تطبيق Angular حديث بدون وحدات. أحد أسباب ذلك هو حقيقة أن ViewEngine يضيف جميع البيانات التعريفية اللازمة إلى الوحدات النمطية.
اللبلاب ، من ناحية أخرى ، يأخذ نهجا مختلفا. في اللبلاب ، يمكن أن يوجد مكون بدون وحدة نمطية. هذا ممكن بفضل مفهوم المحلة. جوهرها هو أن جميع البيانات الوصفية محلية للمكون.
دعنا نفسر هذا من خلال تحليل حزمة es2015 التي تم إنشاؤها باستخدام اللبلاب.
تم إنشاء حزمة Es2015 باستخدام اللبلابفي قسم
Component code
، يمكنك أن ترى أن نظام Ivy قد حفظ رمز المكون. لا يوجد شيء خاص هنا. ولكن بعد ذلك يضيف اللبلاب بعض البيانات الوصفية إلى الكود.
يظهر الجزء الأول من البيانات الوصفية في الشكل
Component factory
. المصنع يعرف كيفية إنشاء مثيل لمكون. في قسم
Component metadata
، يضع Ivy سمات إضافية ، مثل
type
selectors
، أي كل ما يحتاجه المكون أثناء تنفيذ البرنامج.
تجدر الإشارة إلى أن اللبلاب يضيف وظيفة
template
هنا. يتم عرضه في
Compiled version of your template
قسم
Compiled version of your template
. دعونا نتناول هذه الحقيقة المثيرة للاهتمام بمزيد من التفصيل.
وظيفة
template
هي نسخة مجمعة من كود HTML الخاص بنا. تتبع تعليمات Ivy لإنشاء DOM. هذا يختلف عن طريقة عمل ViewEngine.
يأخذ نظام ViewEngine الكود ويتجاوزه. الزاوي سوف ينفذ الكود إذا استخدمناه.
ومع النهج الذي تستخدمه Ivy ، فإن المكون الذي يستدعي الأوامر Angular هو المسؤول عن كل شيء. يتيح هذا التغيير وجود المكونات بشكل مستقل ، وهذا يؤدي إلى حقيقة أنه يمكن تطبيق خوارزمية اهتزاز الأشجار على الكود الأساسي الزاوي.
مثال حقيقي للتحميل مكون كسول
الآن بعد أن علمنا أن تحميل المكونات البطيئة أمر ممكن ، ففكر في ذلك بمثال حقيقي. وهي ، سنقوم بإنشاء تطبيق اختبار يسأل أسئلة المستخدم مع خيارات الإجابة.
يعرض التطبيق صورة للمدينة وخيارات تحتاج منها إلى اختيار اسم هذه المدينة. بمجرد أن يحدد المستخدم خيارًا بالنقر فوق الزر المقابل ، سيتغير هذا الزر فورًا ، مع الإشارة إلى ما إذا كانت الإجابة صحيحة أم خاطئة. إذا تحولت خلفية الزر إلى اللون الأخضر ، فستكون الإجابة صحيحة. إذا تحولت الخلفية إلى اللون الأحمر ، فهذا يعني أن الإجابة غير صحيحة.
بعد تلقي إجابة السؤال الحالي ، يعرض البرنامج السؤال التالي. هنا هو كيف يبدو.
مسابقة تجريبييتم تمثيل الأسئلة التي يسألها البرنامج بواسطة المكون
QuizCardComponent
.
كسول مفهوم تحميل عنصر
دعونا أولاً توضيح الفكرة العامة للتحميل
QuizCardComponent
لمكون
QuizCardComponent
.
عملية مكون QuizCardComponentبعد أن يبدأ المستخدم الاختبار من خلال النقر على زر
Start quiz
، نبدأ عملية التحميل البطيء للمكون. بعد أن يكون المكون تحت تصرفنا ، نضعه في حاوية.
نحن نرد على السؤالالأحداث التي تم حذفها من مكون "كسول" بنفس الطريقة التي نرد بها على أحداث المكون العادي. وهي ، بعد وقوع الحدث ، نعرض على الشاشة البطاقة التالية مع السؤال.
تحليل الكود
لفهم ما يحدث أثناء التحميل
QuizCardComponent
لأحد المكونات ، نبدأ بإصدار مبسط من
QuizCardComponent
، والذي يعرض خصائص السؤال.
ثم سنقوم بتوسيع هذا المكون باستخدام مكونات Angular Material فيه. وفي النهاية ، سنقوم بضبط رد الفعل على الأحداث الناتجة عن المكون.
QuizCardComponent
لإصدار مبسط من مكون
QuizCardComponent
، الذي يحتوي على القالب التالي:
<h1>Here's the question</h1> <ul> <li><b>Image: </b> {{ question.image }}</li> <li><b>Possible selections: </b> {{ question.possibleSelections.toString() }}</li> <li><b>Correct answer: </b> {{ question.correctAnswer }}</li> </ul>
الخطوة الأولى هي إنشاء عنصر حاوية. للقيام بذلك ، يمكننا إما استخدام عنصر حقيقي ، مثل
<div>
، أو - اللجوء إلى
ng-container
، والذي يسمح لنا بالاستغناء عن مستوى إضافي من تعليمات HTML البرمجية. هذا ما يبدو عليه إعلان عنصر الحاوية الذي نضع فيه المكون "الكسول":
<mat-toolbar color="primary"> <span>City quiz</span> </mat-toolbar> <button *ngIf="!quizStarted" mat-raised-button color="primary" class="start-quiz-button" (click)="startQuiz()">Start quiz</button> <ng-container #quizContainer class="quiz-card-container"></ng-container>
في المكون تحتاج إلى الوصول إلى الحاوية. للقيام بذلك ، نستخدم التعليق التوضيحي
@ViewChild
وإعلامنا بأننا نريد قراءة
ViewContainerRef
.
لاحظ أنه في Angular 9 ، يتم تعيين الخاصية
static
في التعليقات التوضيحية
@ViewChild
على "
false
افتراضيًا:
@ViewChild('quizContainer', {read: ViewContainerRef}) quizContainer: ViewContainerRef;
الآن لدينا حاوية نريد أن نضيف فيها عنصر "كسول". بعد ذلك ، نحن بحاجة إلى
ComponentFactoryResolver
Injector
. كلاهما يمكن الحصول عليها عن طريق اللجوء إلى منهجية حقن التبعية.
كيان
ComponentFactoryResolver
هو سجل بسيط يقوم بتأسيس العلاقة بين
ComponentFactory
وفئات
ComponentFactory
التي يتم إنشاؤها تلقائيًا والتي يمكن استخدامها لإنشاء مثيل للمكونات:
constructor(private quizservice: QuizService, private cfr: ComponentFactoryResolver, private injector: Injector) { }
الآن لدينا كل ما هو مطلوب لتحقيق الهدف.
startQuiz
نعمل على محتويات طريقة
startQuiz
وتنظيم التحميل
startQuiz
للمكون:
const {QuizCardComponent} = await import('./quiz-card/quiz-card.component'); const quizCardFactory = this.cfr.resolveComponentFactory(QuizCardComponent); const {instance} = this.quizContainer.createComponent(quizCardFactory, null, this.injector); instance.question = this.quizservice.getNextQuestion();
يمكننا استخدام الأمر
import
ECMAScript لتنظيم التحميل
QuizCardComponent
لـ
QuizCardComponent
. تعبير استيراد بإرجاع وعد. يمكنك العمل معه إما باستخدام إنشاء
async/await
أو باستخدام معالج
.then
. بعد حل الوعد ، نستخدم التدمير لإنشاء مثيل للمكون.
في هذه الأيام ، يجب عليك استخدام
ComponentFactory
لتوفير التوافق مع الإصدارات السابقة. في المستقبل ، السطر المقابل غير مطلوب ، حيث سنكون قادرين على العمل مع المكون مباشرةً.
مصنع
ComponentFactory
يعطينا
componentRef
. نقوم بتمرير
componentRef
و
Injector
إلى طريقة
createComponent
للحاوية.
إرجاع الأسلوب
createComponent
ComponentRef
حيث يوجد مثيل المكون. نستخدم
instance
لتمرير
@Input
المكون
@Input
.
في المستقبل ، يمكن القيام بكل هذا باستخدام طريقة Angular
renderComponent
. هذه الطريقة لا تزال التجريبية. ومع ذلك ، فمن المحتمل جدًا أن تتحول إلى طريقة الزاوي العادية.
فيما يلي مواد مفيدة حول هذا الموضوع.
هذا هو كل ما هو مطلوب لتنظيم التحميل البطيء للمكون.
تحميل عنصر كسولبعد الضغط على زر
Start quiz
، يبدأ التحميل البطيء للمكون. إذا قمت بفتح علامة تبويب
Network
في أدوات المطور ، يمكنك مشاهدة عملية تحميل جزء الشفرة بالكسل الذي يمثله ملف
quiz-card-quiz-card-component.js
. بعد تحميل ومعالجة المكون ، يرى المستخدم بطاقة سؤال.
ملحق المكون
نقوم حاليًا بتحميل مكون
QuizCardComponent
. هذا جيد جدا لكن طلبنا لا يعمل بشكل خاص بعد.
دعونا إصلاح هذا عن طريق إضافة ميزات ومكونات إضافية من Angular Material إليها:
<mat-card class="quiz-card"> <mat-card-header> <div mat-card-avatar class="quiz-header-image"></div> <mat-card-title>Which city is this?</mat-card-title> <mat-card-subtitle>Click on the correct answer below</mat-card-subtitle> </mat-card-header> <img class="image" mat-card-image [src]="'assets/' + question.image" alt="Photo of a Shiba Inu"> <mat-card-actions class="answer-section"> <button [disabled]="answeredCorrectly !== undefined" *ngFor="let selection of question.possibleSelections" mat-stroked-button color="primary" [ngClass]="{ 'correct': answeredCorrectly && selection === question.correctAnswer, 'wrong': answeredCorrectly === false && selection === question.correctAnswer }" (click)="answer(selection)"> {{selection}} </button> </mat-card-actions> </mat-card>
لقد قمنا بإدراج بعض مكونات المواد الجميلة في المكون. ماذا عن الوحدات المقابلة؟
يمكن بالطبع إضافتها إلى
AppModule
. ولكن هذا يعني أن هذه الوحدات سيتم تحميلها في وضع "الجشع". وهذه ليست فكرة جيدة. علاوة على ذلك ، سوف تفشل تجميع المشروع ، مع الرسالة التالية:
ERROR in src/app/quiz-card/quiz-card.component.html:9:1 - error TS-998001: 'mat-card' is not a known element: 1. If 'mat-card' is an Angular component, then verify that it is part of this module. 2. If 'mat-card' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
ما يجب القيام به كما ربما تكون قد فهمت بالفعل ، فإن هذه المشكلة قابلة للحل تمامًا. ويمكن القيام بذلك باستخدام وحدات.
لكن هذه المرة سوف نستخدمها بشكل مختلف قليلاً عن ذي قبل. سنضيف وحدة نمطية صغيرة إلى نفس ملف
QuizCardComponent
(في ملف
quizcard.component.ts
):
@NgModule({ declarations: [QuizCardComponent], imports: [CommonModule, MatCardModule, MatButtonModule] }) class QuizCardModule { }
لاحظ أنه لا يوجد بيان
export
.
تنتمي مواصفات الوحدة هذه إلى المكون الخاص بنا الذي تم تحميله في وضع الكسول. نتيجة لذلك ، سيكون المكون الوحيد الذي تم الإعلان عنه في الوحدة النمطية هو
QuizCardComponent
.
import
قسم الاستيراد الوحدات النمطية التي يحتاجها مكوننا فقط.
لا نقوم بتصدير الوحدة النمطية الجديدة حتى لا تتمكن الوحدات النمطية المحمّلة في وضع الجشع من استيرادها.
أعد تشغيل التطبيق وانظر كيف يتصرف عند النقر فوق
Start quiz
.
تعديل تطبيق التحليل! رائع
QuizCardComponent
تحميل المكون
QuizCardComponent
في وضع الكسول وإضافته إلى
ViewContainer
. يتم تحميل جميع التبعيات اللازمة معها.
سنقوم بتحليل حزمة التطبيق باستخدام أداة
webpack-bundle-analyze
الويب التي توفرها وحدة npm المقابلة. يسمح لك بتصور محتويات الملفات التي ينتجها Webpack وفحص المخطط الناتج.
تحليل حزمة التطبيقحجم الحزمة الرئيسية للتطبيق حوالي 260 كيلو بايت. إذا قمنا بتنزيل مكون
QuizCardComponent
معه ، فسيكون حجم البيانات التي تم تنزيلها حوالي 270 كيلو بايت. اتضح أننا تمكنا من تقليل حجم الحزمة الرئيسية بمقدار 10 كيلو بايت ، وتحميل مكون واحد فقط في وضع الكسول. نتيجة جيدة!
يحصل رمز
QuizCardComponent
بعد التجميع على ملف منفصل. إذا قمت بتحليل محتويات هذا الملف ، اتضح أنه لا
QuizCardComponent
رمز
QuizCardComponent
فقط ، ولكن أيضًا وحدات المواد المستخدمة في هذا المكون.
على الرغم من أن
QuizCardComponent
يستخدم
MatButtonModule
و
MatCardModule
، فإن
MatCardModule
فقط يحصل على
MatCardModule
التعليمات البرمجية النهائي. السبب في ذلك هو أننا نستخدم
MatButtonModule
في
AppModule
، مع عرض زر
Start quiz
. نتيجة لذلك ، يقع الكود المقابل في جزء آخر من الحزمة.
الآن قمنا بتنظيم التحميل
QuizCardComponent
لـ
QuizCardComponent
. يعرض هذا المكون بطاقة ، مصممة على نمط المادة ، تحتوي على صورة وسؤال وأزرار مع خيارات الإجابة. ماذا يحدث الآن إذا نقرت على أحد هذه الأزرار؟
يصبح الزر ، اعتمادًا على ما إذا كان يحتوي على إجابة صحيحة أو خاطئة ، أخضر أو أحمر عند الضغط عليه. ماذا يحدث؟ لا تهتم. ونحن بحاجة إلى أنه بعد الإجابة على سؤال ، سيتم عرض بطاقة سؤال آخر. إصلاحه.
التعامل مع الحدث
لا يُظهر التطبيق بطاقة أسئلة جديدة عند النقر فوق زر الإجابة بسبب حقيقة أننا لم نقم بعد بإعداد آليات للاستجابة لأحداث المكونات المحملة في وضع الكسل. نحن نعلم بالفعل أن مكون
QuizCardComponent
ينشئ أحداثًا باستخدام
EventEmitter
. إذا نظرت إلى تعريف فئة
EventEmitter
، يمكنك معرفة أنه سليل
Subject
:
export declare class EventEmitter<T extends any> extends Subject<T>
هذا يعني أن
EventEmitter
لديه طريقة
subscribe
، والتي تتيح لك تكوين استجابة النظام للأحداث التي تحدث:
instance.questionAnswered.pipe( takeUntil(instance.destroy$) ).subscribe(() => this.showNewQuestion());
نحن هنا نشترك في
questionAnswered
وندعو طريقة
showNextQuestion
، التي تنفذ منطق
lazyLoadQuizCard
:
async showNewQuestion() { this.lazyLoadQuizCard(); }
هناك حاجة إلى إنشاء
takeUntil(instance.destroy$)
لمسح الاشتراك الذي يتم تنفيذه بعد إتلاف المكون. إذا تم
ngOnDestroy
ربط
ngOnDestroy
لدورة حياة مكون
ngOnDestroy
يتم استدعاء
destroy$
مع
next
complete
.
نظرًا لأن المكون تم تحميله بالفعل ، فإن النظام لا يقوم بتنفيذ طلب HTTP إضافي. نستخدم محتويات جزء الكود الذي لدينا بالفعل ، وننشئ مكونًا جديدًا ونضعه في الحاوية.
السنانير دورة حياة المكون
يتم استدعاء جميع
QuizCardComponent
دورة حياة المكونات تقريبًا تلقائيًا عند العمل مع مكون
QuizCardComponent
باستخدام تقنية التحميل البطيئة. لكن ربط واحد لا يكفي. هل يمكنك فهم أي واحد؟
السنانير دورة حياة المكونهذا هو ربط
ngOnChanges
الأكثر أهمية. نظرًا لأننا نحن نقوم بتحديث خصائص المدخلات الخاصة بمثيل المكون ، فنحن مسؤولون أيضًا عن استدعاء ربط دورة حياة
ngOnChanges
.
للاتصال
ngOnChanges
لمثيل
ngOnChanges
، تحتاج إلى إنشاء كائن
SimpleChange
بنفسك:
(instance as any).ngOnChanges({ question: new SimpleChange(null, instance.question, true) });
نحن نسمي
ngOnChanges
يدويًا مثيل المكون
SimpleChange
كائن
SimpleChange
. يشير هذا الكائن إلى أن هذا التغيير هو الأول ، وأن القيمة السابقة
null
، وأن القيمة الحالية هي سؤال.
! رائع قمنا بتحميل المكون بوحدات تابعة لجهة خارجية ، واستجابنا للأحداث التي أنشأها وقمنا بإعداد اتصال الخطافات اللازمة لدورة حياة المكون.
فيما يلي الكود المصدري للمشروع النهائي الذي كنا نعمل عليه هنا.
النتائج
يتيح تحميل المكونات الكسولة للمطورين الزاويين فرصًا رائعة لتحسين أداء التطبيق. تحت تصرفه هي الأدوات لضبط تكوين المواد المحملة في وضع الكسول جدًا. في السابق ، عندما كان من الممكن تحميل المسارات فقط في الوضع الكسول ، لم يكن لدينا هذه الدقة.
لسوء الحظ ، عند استخدام الوحدات الخارجية في المكون ، نحتاج أيضًا إلى الاهتمام بالوحدات النمطية. ومع ذلك ، تجدر الإشارة إلى أن هذا قد يتغير في المستقبل.
قدم محرك Ivy مفهوم محلية ، وذلك بفضل المكونات التي يمكن أن توجد بشكل مستقل. هذا التغيير هو أساس مستقبل الزاوي.
أعزائي القراء! هل تخطط لاستخدام تقنية تحميل المكونات البطيئة في مشاريعك الزاوية؟
