الالتفافية الزاوي الالتفافية وتوفير الوقت

مع الزاوي ، يمكنك أن تفعل أي شيء. أو كل شيء تقريبا. لكن في بعض الأحيان يؤدي هذا "شبه" غادر إلى حقيقة أن المطور يهدر الوقت عن طريق إنشاء حلول ، أو محاولة فهم سبب حدوث شيء ما ، أو لماذا لا يعمل شيء ما كما هو متوقع.



يقول مؤلف المقالة التي نترجمها اليوم إنه يريد مشاركة النصائح التي ستساعد مطوري Angular على توفير بعض الوقت. سيتحدث عن مصاعب Angular ، التي أتيحت له (وليس هو فقط) فرصة مقابلته.

رقم 1. التوجيه المخصص الذي طبقته لا يعمل


لذلك ، وجدت توجيه Angular لطيفًا تابعًا لجهة خارجية وقررت استخدامه مع العناصر القياسية في قالب Angular. ! رائع دعنا نحاول القيام بذلك:

<span awesomeTooltip="'Tooltip text'"> </span> 

يمكنك بدء التطبيق ... ولا شيء يحدث. أنت ، مثل أي مبرمج عادي ذي خبرة ، تنظر إلى وحدة التحكم في أدوات Developer Developer. وأنت لا ترى أي شيء هناك. التوجيه لا يعمل والزاوي الصامت.

ثم يقرر بعض الرؤوس المشرقة من فريقك وضع التوجيه بين قوسين مربعين.

 <span [awesomeTooltip]="'Tooltip text'"> </span> 

بعد ذلك ، بعد أن فقدنا بعض الوقت ، نرى ما يلي في وحدة التحكم.


إليك الشيء: لقد نسينا فقط استيراد الوحدة النمطية مع التوجيه

الآن سبب المشكلة واضح تمامًا: لقد نسينا فقط استيراد الوحدة النمطية للتوجيه إلى وحدة التطبيق الزاوي.
هذا يؤدي إلى قاعدة مهمة: لا تستخدم التوجيهات دون أقواس مربعة.

يمكنك تجربة التوجيهات هنا .

رقم 2. إرجاع ViewChild غير محدد


افترض أنك أنشأت رابطًا لعنصر إدخال النص الموضح في قالب Angular.

 <input type="text" name="fname" #inputTag> 

أنت بصدد استخدام وظيفة RxJS fromEvent لإنشاء تدفق يسقط فيه ما تم إدخاله في الحقل. للقيام بذلك ، تحتاج إلى رابط إلى حقل الإدخال ، والذي يمكن الحصول عليه باستخدام مصمم Angular ViewChild :

 class SomeComponent implements AfterViewInit {    @ViewChild('inputTag') inputTag: ElementRef;    ngAfterViewInit(){        const input$ = fromEvent(this.inputTag.nativeElement, 'keyUp')    } ...   } 

هنا ، باستخدام وظيفة RxJS fromEvent ، نقوم بإنشاء دفق تسقط فيه البيانات التي يتم إدخالها في الحقل.
اختبار هذا الرمز.


خطأ

ماذا حدث

في الواقع ، تنطبق القاعدة التالية هنا: إذا قام ViewChild بإرجاع undefined ، فابحث عن *ngIf في القالب.

 <div *ngIf="someCondition">    <input type="text" name="fname" #inputTag> </div> 

هنا هو الجاني من المشكلة.

بالإضافة إلى ذلك ، تحقق من القالب للحصول على توجيهات هيكلية أخرى أو ng-template أعلى عنصر المشكلة.
النظر في الحلول الممكنة لهذه المشكلة.

▍ البديل من حل المشكلة №1


يمكنك ببساطة إخفاء عنصر القالب إذا كنت لا تحتاج إليه. في هذه الحالة ، سيظل العنصر موجودًا دائمًا ViewChild من إرجاع رابط إليه في ngAfterViewInit .

 <div [hidden]="!someCondition">    <input type="text" name="fname" #inputTag> </div> 

▍ البديل من حل المشكلة №2


هناك طريقة أخرى لحل هذه المشكلة وهي استخدام المستوطنين.

 class SomeComponent {   @ViewChild('inputTag') set inputTag(input: ElementRef|null) {    if(!input) return;       this.doSomething(input);  }  doSomething(input) {    const input$ = keysfromEvent(input.nativeElement, 'keyup');    ...  } } 

هنا ، بمجرد أن تقوم Angular بتعيين قيمة محددة لخاصية inputTag ، فإننا نقوم بإنشاء دفق من البيانات المدخلة في حقل الإدخال.

فيما يلي بعض الموارد المفيدة المتعلقة بهذه المشكلة:

  • هنا يمكنك أن تقرأ أن نتائج ViewChild في Angular 8 يمكن أن تكون ثابتة وديناميكية.
  • إذا كنت تواجه صعوبة في العمل مع RxJs - ألق نظرة على دورة الفيديو هذه .

رقم 3. تنفيذ التعليمات البرمجية عند تحديث القائمة التي تم إنشاؤها باستخدام * ngFor (بعد ظهور العناصر في DOM)


افترض أن لديك بعض التوجيه المخصص المثير للاهتمام لتنظيم قوائم قابلة للتمرير. ستقوم بتطبيقه على قائمة يتم إنشاؤها باستخدام توجيه Angular *ngFor .

 <div *ngFor="let item of itemsList; let i = index;"     [customScroll]     >  <p *ngFor="let item of items" class="list-item">{{item}}</p>   </div> 

عادة ، في مثل هذه الحالات ، عند تحديث القائمة ، تحتاج إلى استدعاء شيء مثل scrollDirective.update لتكوين سلوك التمرير ، مع مراعاة التغييرات التي حدثت في القائمة.

قد يبدو أنه يمكن القيام بذلك باستخدام ربط ngOnChanges :

 class SomeComponent implements OnChanges {  @Input() itemsList = [];   @ViewChild(CustomScrollDirective) scroll: CustomScrollDirective;  ngOnChanges(changes) {    if (changes.itemsList) {      this.scroll.update();    }  } ... } 

صحيح ، نحن هنا نواجه مشكلة. يسمى الخطاف قبل أن يعرض المتصفح القائمة المحدثة. نتيجة لذلك ، تتم عملية إعادة حساب معلمات التوجيه الخاصة بتمرير القائمة بشكل غير صحيح.

كيفية إجراء مكالمة مباشرة بعد انتهاء *ngFor العمل؟

يمكنك القيام بذلك باتباع الخطوات الثلاث البسيطة التالية:

رقم الخطوة 1


ضع الروابط إلى العناصر التي يتم فيها *ngFor ( #listItems ).

 <div [customScroll]>    <p *ngFor="let item of items" #listItems>{{item}}</p> </div> 

رقم الخطوة 2


الحصول على قائمة من هذه العناصر باستخدام الديكور Angular ViewChildren . تقوم بإرجاع كيان من نوع QueryList .

الخطوة رقم 3


QueryList فئة QueryList على خاصية التغييرات للقراءة فقط التي QueryList الأحداث في كل مرة تتغير فيها القائمة.

 class SomeComponent implements AfterViewInit {  @Input() itemsList = [];   @ViewChild(CustomScrollDirective) scroll: CustomScrollDirective;  @ViewChildren('listItems') listItems: QueryList<any>;  private sub: Subscription;   ngAfterViewInit() {    this.sub = this.listItems.changes.subscribe(() => this.scroll.update())  } ... } 

الآن تم حل المشكلة. هنا يمكنك تجربة المثال المقابل.

رقم 4. مشاكل مع ActivatedRoute.queryParam التي تحدث عندما يمكن تشغيل الاستعلامات بدون معلمات


سوف يساعدنا الكود التالي في فهم جوهر هذه المشكلة.

 // app-routing.module.ts const routes: Routes = [    {path: '', redirectTo: '/home', pathMatch: 'full'},    {path: 'home', component: HomeComponent},  ];   @NgModule({    imports: [RouterModule.forRoot(routes)], //  #1    exports: [RouterModule]  })  export class AppRoutingModule { }    //app.module.ts  @NgModule({    ...    bootstrap: [AppComponent] //  #2  })  export class AppModule { }   // app.component.html  <router-outlet></router-outlet> //  #3    // app.component.ts  export class AppComponent implements OnInit {    title = 'QueryTest';     constructor(private route: ActivatedRoute) { }     ngOnInit() {      this.route.queryParams          .subscribe(params => {            console.log('saveToken', params); //  #4          });    }  } 

يتم إجراء بعض الأجزاء من هذا الرمز تعليقات مثل #x . النظر فيها:

  1. في الوحدة الرئيسية للتطبيق ، حددنا الطرق RouterModule بإضافة RouterModule هناك. يتم تكوين المسارات بحيث إذا لم يتم توفير مسار في عنوان URL ، فإننا نعيد توجيه المستخدم إلى /home الصفحة /home .
  2. كمكون للتنزيل ، نحدد في الوحدة الرئيسية AppComponent .
  3. يستخدم AppComponent <router-outlet> لإخراج مكونات <router-outlet> المناسبة.
  4. الآن الشيء الأكثر أهمية. نحن بحاجة إلى الحصول على queryParams للطريق من URL

لنفترض أننا حصلنا على عنوان URL التالي:

 https://localhost:4400/home?accessToken=someTokenSequence 

في هذه الحالة ، queryParams كما يلي:

 {accessToken: 'someTokenSequence'} 

دعونا نلقي نظرة على عمل كل هذا في المتصفح.


اختبار تطبيق يقوم بتطبيق نظام توجيه

هنا قد يكون لديك سؤال حول جوهر المشكلة. حصلنا على المعلمات ، كل شيء يعمل كما هو متوقع ...

ألق نظرة على لقطة الشاشة أعلاه للمتصفح ، وما يتم عرضه في وحدة التحكم. هنا يمكنك أن ترى أن كائن queryParams يصدر مرتين. الكائن الأول فارغ ؛ يتم إصداره أثناء عملية التهيئة لجهاز التوجيه الزاوي. بعد ذلك فقط نحصل على كائن يحتوي على معلمات الطلب (في حالتنا - {accessToken: 'someTokenSequence'} ).

المشكلة هي أنه في حالة عدم وجود معلمات طلب في عنوان URL ، فلن يعرض الموجه أي شيء. هذا - بعد إصدار أول كائن فارغ ، لن يتم إصدار الكائن الثاني الفارغ أيضًا ، والذي قد يشير إلى عدم وجود معلمات.


عند تنفيذ طلب بدون معلمات ، لا يتم إصدار الكائن الثاني

نتيجةً لذلك ، اتضح أنه إذا كانت الشفرة تتوقع كائنًا ثانيًا يمكنه من خلالها تلقي بيانات الطلب ، فلن يتم تشغيله إذا لم تكن هناك معلمات طلب في عنوان URL.

كيفية حل هذه المشكلة؟ هنا RxJs يمكن أن تساعدنا. سنقوم بإنشاء كائنين يمكن ملاحظتهما على أساس ActivatedRoute.queryParams . كالعادة - فكر في حل المشكلة خطوة بخطوة.

رقم الخطوة 1


أول كائن يمكن ملاحظته ، paramsInUrl$ ، paramsInUrl$ البيانات إذا لم تكن queryParams فارغة:

 export class AppComponent implements OnInit {    constructor(private route: ActivatedRoute,                private locationService: Location) {    }    ngOnInit() {             //            //              const paramsInUrl$ = this.route.queryParams.pipe(            filter(params => Object.keys(params).length > 0)        );     ...    } } 

رقم الخطوة 2


الكائن الثاني الذي يمكن ملاحظته ، noParamsInUrl$ ، سيعيد قيمة فارغة فقط إذا لم يتم العثور على معلمات طلب في عنوان URL:

 export class AppComponent implements OnInit {    title = 'QueryTest';    constructor(private route: ActivatedRoute,                private locationService: Location) {    }    ngOnInit() {                 ...        //   ,     ,   URL        //             const noParamsInUrl$ = this.route.queryParams.pipe(            filter(() => !this.locationService.path().includes('?')),            map(() => ({}))        );            ...    } } 

الخطوة رقم 3


الآن ادمج الكائنات التي تمت ملاحظتها باستخدام وظيفة دمج RxJS:

 export class AppComponent implements OnInit {    title = 'QueryTest';    constructor(private route: ActivatedRoute,                private locationService: Location) {    }    ngOnInit() {             //            //              const paramsInUrl$ = this.route.queryParams.pipe(            filter(params => Object.keys(params).length > 0)        );        //   ,     ,   URL        //             const noParamsInUrl$ = this.route.queryParams.pipe(            filter(() => !this.locationService.path().includes('?')),            map(() => ({}))        );        const params$ = merge(paramsInUrl$, noParamsInUrl$);        params$.subscribe(params => {            console.log('saveToken', params);        });    } } 

الآن param$ كائن param$ المرصود قيمة مرة واحدة فقط - بغض النظر عما إذا كان هناك شيء ما queryParams في queryParams (يتم queryParams كائن به معلمات استعلام) أم لا (يتم queryParams كائن فارغ).

يمكنك تجربة هذا الرمز هنا .

رقم 5. الصفحات البطيئة


افترض أن لديك مكونًا يعرض بعض البيانات المنسقة:

 // home.component.html <div class="wrapper" (mousemove)="mouseCoordinates = {x: $event.x, y: $event.y}">  <div *ngFor="let item of items">     <span>{{formatItem(item)}}</span>  </div> </div> {{mouseCoordinates | json}} // home.component.ts export class HomeComponent {    items = [1, 2, 3, 4, 5, 6];    mouseCoordinates = {};    formatItem(item) {              //           const t = Array.apply(null, Array(5)).map(() => 1);             console.log('formatItem');        return item + '%';    } } 

هذا المكون يحل مشكلتين:

  1. يعرض مجموعة من العناصر (من المفترض أن يتم تنفيذ هذه العملية مرة واحدة). بالإضافة إلى ذلك ، يقوم formatItem ما يتم عرضه عن طريق استدعاء الأسلوب formatItem .
  2. يعرض إحداثيات الماوس (من الواضح أنه سيتم تحديث هذه القيمة كثيرًا).

لا تتوقع أن يكون لهذا المكون أية مشكلات متعلقة بالأداء. لذلك ، قم بإجراء اختبار أداء فقط للامتثال لجميع الإجراءات. ومع ذلك ، تظهر بعض الشذوذ خلال هذا الاختبار.


الكثير من المكالمات formatItem والكثير من تحميل وحدة المعالجة المركزية

ما هو الموضوع؟ ولكن الحقيقة هي أنه عندما يعيد Angular رسم القالب ، فإنه يستدعي جميع الوظائف من القالب (في حالتنا ، وظيفة formatItem ). نتيجة لذلك ، إذا تم إجراء أي حسابات ثقيلة في وظائف القالب ، فإن هذا يضع ضغطًا على المعالج ويؤثر على كيفية رؤية المستخدمين للصفحة المقابلة.

كيفية اصلاحها؟ يكفي إجراء العمليات الحسابية التي يتم إجراؤها formatItem مقدمًا ، وعرض البيانات الجاهزة بالفعل على الصفحة.

 // home.component.html <div class="wrapper" (mousemove)="mouseCoordinates = {x: $event.x, y: $event.y}">  <div *ngFor="let item of displayedItems">    <span>{{item}}</span>  </div> </div> {{mouseCoordinates | json}} // home.component.ts @Component({    selector: 'app-home',    templateUrl: './home.component.html',    styleUrls: ['./home.component.sass'] }) export class HomeComponent implements OnInit {    items = [1, 2, 3, 4, 5, 6];    displayedItems = [];    mouseCoordinates = {};    ngOnInit() {        this.displayedItems = this.items.map((item) => this.formatItem(item));    }    formatItem(item) {        console.log('formatItem');        const t = Array.apply(null, Array(5)).map(() => 1);        return item + '%';    } } 

الآن اختبار الأداء يبدو أكثر لائقة.


6 مكالمات بتنسيقات فقط وتحميل معالج منخفض

الآن التطبيق يعمل بشكل أفضل بكثير. لكن الحل المستخدم هنا يحتوي على بعض الميزات التي لا تكون دائمًا ممتعة:

  • نظرًا لأننا نعرض إحداثيات الماوس في القالب ، فإن حدث mousemove لا يزال يشغل mousemove التغيير. ولكن بما أننا نحتاج إلى إحداثيات الماوس ، فلا يمكننا التخلص من هذا.
  • إذا كان معالج أحداث mousemove يحتاج فقط إلى إجراء بعض العمليات الحسابية (التي لا تؤثر على ما يتم عرضه على الصفحة) ، ثم لتسريع التطبيق ، يمكنك القيام بما يلي:

    1. يمكنك استخدام NgZone.runOutsideOfAngular داخل وظيفة معالج الأحداث. هذا يمنع التحقق من التغييرات من بدء عند mousemove الحدث mousemove (سيؤثر هذا فقط على هذا المعالج).
    2. يمكنك منع تصحيح zone.js لبعض الأحداث باستخدام سطر التعليمات البرمجية التالي في polyfills.ts . سيؤثر هذا على تطبيق الزاوي بأكمله.

 * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; //      

إذا كنت مهتمًا بمسائل تحسين أداء التطبيقات الزاوي - هنا ، هنا ، هنا - هنا - مواد مفيدة حول هذا الموضوع.

النتائج


الآن وقد قرأت هذه المقالة ، يجب أن تظهر 5 أدوات جديدة في ترسانة Angular-developer الخاصة بك والتي يمكنك من خلالها حل بعض المشاكل الشائعة. نأمل أن تساعدك النصائح التي وجدتها هنا على توفير بعض الوقت.

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

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


All Articles