الزاوي 9 واللبلاب: تحميل مكون كسول

تحميل عنصر كسول في الزاوي؟ ربما نحن نتحدث عن وحدات التحميل البطيئة باستخدام جهاز التوجيه الزاوي؟ لا ، نحن نتحدث عن المكونات. الإصدار الحالي من 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 مفهوم محلية ، وذلك بفضل المكونات التي يمكن أن توجد بشكل مستقل. هذا التغيير هو أساس مستقبل الزاوي.

أعزائي القراء! هل تخطط لاستخدام تقنية تحميل المكونات البطيئة في مشاريعك الزاوية؟

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


All Articles