Bypass Pitfall Angular dan Penghematan Waktu

Dengan Angular, Anda dapat melakukan apa saja. Atau hampir semuanya. Tetapi kadang-kadang “hampir” yang berbahaya ini mengarah pada fakta bahwa pengembang membuang-buang waktu dengan menciptakan solusi, atau mencoba memahami mengapa sesuatu terjadi, atau mengapa sesuatu tidak berfungsi seperti yang diharapkan.



Penulis artikel yang kami terjemahkan hari ini mengatakan ia ingin berbagi kiat yang akan membantu pengembang Angular menghemat waktu. Dia akan berbicara tentang perangkap Angular, yang dia (dan bukan hanya dia) memiliki kesempatan untuk bertemu.

1 Arahan khusus yang Anda terapkan tidak berfungsi


Jadi, Anda menemukan arahan pihak ketiga yang bagus dan memutuskan untuk menggunakannya dengan elemen standar di templat Angular. Hebat! Mari kita coba lakukan ini:

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

Anda memulai aplikasi ... Dan tidak ada yang terjadi. Anda, seperti programmer normal dan berpengalaman lainnya, memeriksa konsol Alat Pengembang Chrome. Dan Anda tidak melihat apa pun di sana. Arahan tidak berfungsi dan Angular diam.

Kemudian beberapa kepala cemerlang dari tim Anda memutuskan untuk menempatkan arahan dalam tanda kurung.

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

Setelah itu, setelah kehilangan sedikit waktu, kami melihat yang berikut di konsol.


Begini masalahnya: kami hanya lupa mengimpor modul dengan arahan

Sekarang penyebab masalahnya cukup jelas: kami hanya lupa mengimpor modul direktif ke modul aplikasi Angular.
Ini mengarah pada aturan penting: jangan pernah gunakan arahan tanpa tanda kurung.

Anda dapat bereksperimen dengan arahan di sini .

2 ViewChild mengembalikan undefined


Misalkan Anda membuat tautan ke elemen entri teks yang dijelaskan dalam templat Angular.

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

Anda akan pergi, menggunakan fungsi RxJS fromEvent , untuk membuat aliran ke mana apa yang dimasukkan ke dalam bidang akan jatuh. Untuk melakukan ini, Anda memerlukan tautan ke bidang input, yang dapat diperoleh menggunakan dekorator Angular ViewChild :

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

Di sini, menggunakan fungsi RxJS fromEvent , kami membuat aliran ke mana data yang dimasukkan ke dalam bidang akan jatuh.
Uji kode ini.


Kesalahan

Apa yang terjadi

Bahkan, aturan berikut ini berlaku di sini: jika ViewChild mengembalikan yang undefined , cari *ngIf di templat.

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

Di sini dia adalah pelakunya.

Selain itu, periksa template untuk arahan struktural lain atau ng-template atas elemen masalah.
Pertimbangkan kemungkinan solusi untuk masalah ini.

▍Variant untuk memecahkan masalah №1


Anda bisa menyembunyikan elemen template jika tidak membutuhkannya. Dalam hal ini, elemen akan selalu terus ada dan ViewChild akan dapat mengembalikan tautan ke sana di kait ngAfterViewInit .

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

▍Variant untuk memecahkan masalah №2


Cara lain untuk mengatasi masalah ini adalah dengan menggunakan setter.

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

Di sini, segera setelah Angular memberikan nilai tertentu ke properti inputTag , kami membuat aliran dari data yang dimasukkan di bidang input.

Berikut adalah beberapa sumber daya bermanfaat yang terkait dengan masalah ini:

  • Di sini Anda dapat membaca bahwa hasil ViewChild di Angular 8 bisa statis dan dinamis.
  • Jika Anda kesulitan bekerja dengan RxJs - lihat kursus video ini .

Nomor 3 Eksekusi kode ketika memperbarui daftar yang dihasilkan dengan * ngFor (setelah elemen muncul di DOM)


Misalkan Anda memiliki beberapa arahan khusus yang menarik untuk mengatur daftar yang dapat digulir. Anda akan menerapkannya ke daftar yang dibuat menggunakan *ngFor Angular *ngFor .

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

Biasanya, dalam kasus seperti itu, ketika memperbarui daftar, Anda perlu memanggil sesuatu seperti scrollDirective.update untuk mengkonfigurasi perilaku pengguliran, dengan mempertimbangkan perubahan yang telah terjadi dalam daftar.

Tampaknya ini bisa dilakukan dengan menggunakan pengait ngOnChanges :

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

Benar, di sini kita dihadapkan dengan masalah. Pengait dipanggil sebelum browser menampilkan daftar yang diperbarui. Akibatnya, perhitungan ulang parameter direktif untuk menggulir daftar dilakukan dengan tidak benar.

Bagaimana cara melakukan panggilan tepat setelah *ngFor selesai bekerja?

Anda dapat melakukan ini dengan mengikuti 3 langkah sederhana ini:

▍Langkah nomor 1


Letakkan tautan ke elemen tempat *ngFor ( #listItems ) #listItems .

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

▍Langkah nomor 2


Dapatkan daftar elemen-elemen ini menggunakan dekorator ViewChildren Angular. Ini mengembalikan entitas tipe QueryList .

▍Langkah nomor 3


Kelas QueryList memiliki properti perubahan hanya baca yang QueryList acara setiap kali daftar berubah.

 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())  } ... } 

Sekarang masalahnya teratasi. Di sini Anda dapat bereksperimen dengan contoh yang sesuai.

Nomor 4 Masalah dengan ActivatedRoute.queryParam yang Terjadi Ketika Kueri Dapat Dijalankan Tanpa Parameter


Kode berikut akan membantu kami memahami inti dari masalah ini.

 // 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          });    }  } 

Beberapa fragmen dari kode ini dibuat komentar seperti #x . Pertimbangkan mereka:

  1. Dalam modul utama aplikasi, kami mendefinisikan rute dan menambahkan RouterModule sana. Rute dikonfigurasikan sehingga jika rute tidak disediakan dalam URL, kami mengarahkan pengguna ke halaman /home .
  2. Sebagai komponen untuk mengunduh, kami menentukan dalam modul utama AppComponent .
  3. AppComponent menggunakan <router-outlet> untuk menampilkan komponen rute yang sesuai.
  4. Sekarang yang paling penting. Kami perlu mendapatkan queryParams untuk rute dari URL

Misalkan kita mendapat URL berikut:

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

Dalam hal ini, queryParams akan terlihat seperti ini:

 {accessToken: 'someTokenSequence'} 

Mari kita lihat pekerjaan semua ini di browser.


Menguji aplikasi yang mengimplementasikan sistem perutean

Di sini Anda mungkin memiliki pertanyaan tentang esensi masalah. Kami mendapat parameter, semuanya berfungsi seperti yang diharapkan ...

Lihatlah screenshot browser di atas, dan apa yang ditampilkan di konsol. Di sini Anda dapat melihat bahwa objek queryParams dikeluarkan dua kali. Objek pertama kosong, dikeluarkan selama proses inisialisasi router Angular. Hanya setelah itu kita mendapatkan objek yang berisi parameter permintaan (dalam kasus kami - {accessToken: 'someTokenSequence'} ).

Masalahnya adalah bahwa jika tidak ada parameter permintaan di URL, router tidak akan mengembalikan apa pun. Artinya - setelah mengeluarkan objek kosong pertama, objek kedua, juga kosong, yang bisa menunjukkan tidak adanya parameter, tidak akan dikeluarkan.


Saat menjalankan permintaan tanpa parameter, objek kedua tidak dikeluarkan

Akibatnya, ternyata jika kode mengharapkan objek kedua dari mana ia dapat menerima data permintaan, maka itu tidak akan diluncurkan jika tidak ada parameter permintaan di URL.

Bagaimana cara mengatasi masalah ini? Di sini, RxJ dapat membantu kami. Kami akan membuat dua objek yang dapat diamati berdasarkan ActivatedRoute.queryParams . Seperti biasa - pertimbangkan solusi langkah demi langkah untuk masalah tersebut.

▍Langkah nomor 1


Objek yang dapat diamati pertama, paramsInUrl$ , akan paramsInUrl$ data jika queryParams tidak kosong:

 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)        );     ...    } } 

▍Langkah nomor 2


Objek yang dapat diamati kedua, noParamsInUrl$ , akan noParamsInUrl$ nilai kosong hanya jika tidak ada parameter permintaan yang ditemukan di 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(() => ({}))        );            ...    } } 

▍Langkah nomor 3


Sekarang gabungkan objek yang diamati menggunakan fungsi gabungan 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);        });    } } 

Sekarang param$ object yang diamati param$ nilai hanya sekali - terlepas dari apakah sesuatu queryParams dalam queryParams (objek dengan parameter kueri queryParams ) atau tidak (objek kosong queryParams ).

Anda dapat bereksperimen dengan kode ini di sini .

No. 5. Halaman Lambat


Misalkan Anda memiliki komponen yang menampilkan beberapa data yang diformat:

 // 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 + '%';    } } 

Komponen ini memecahkan dua masalah:

  1. Ini menampilkan array elemen (diasumsikan bahwa operasi ini dilakukan sekali). Selain itu, memformat apa yang ditampilkan dengan memanggil metode formatItem .
  2. Ini menampilkan koordinat mouse (nilai ini jelas akan sangat sering diperbarui).

Anda tidak mengharapkan komponen ini memiliki masalah kinerja. Karena itu, jalankan uji kinerja hanya untuk mematuhi semua formalitas. Namun, beberapa keanehan muncul selama tes ini.


Banyak panggilan formatItem dan beban CPU yang cukup banyak

Ada apa? Tetapi kenyataannya adalah bahwa ketika Angular menggambar ulang templat, ia memanggil semua fungsi dari templat (dalam kasus kami, fungsi formatItem ). Akibatnya, jika ada perhitungan berat yang dilakukan dalam fungsi templat, ini akan membuat prosesor tertekan dan memengaruhi cara pengguna melihat laman terkait.

Bagaimana cara memperbaikinya? Cukup dengan melakukan perhitungan yang dilakukan dalam formatItem sebelumnya, dan menampilkan data yang sudah siap di halaman.

 // 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 + '%';    } } 

Sekarang tes kinerja terlihat jauh lebih baik.


Hanya 6 panggilan formatItem dan beban prosesor yang rendah

Sekarang aplikasi bekerja lebih baik. Tetapi solusi yang digunakan di sini memiliki beberapa fitur yang tidak selalu menyenangkan:

  • Karena kami menampilkan koordinat mouse di templat, acara mousemove masih memicu pemeriksaan perubahan. Tapi, karena kita membutuhkan koordinat mouse, kita tidak bisa menyingkirkan ini.
  • Jika pengendali acara mousemove hanya perlu melakukan beberapa perhitungan (yang tidak mempengaruhi apa yang ditampilkan pada halaman), maka untuk mempercepat aplikasi, Anda dapat melakukan hal berikut:

    1. Anda dapat menggunakan NgZone.runOutsideOfAngular di dalam fungsi event handler. Ini mencegah pemeriksaan perubahan dari mulai ketika peristiwa mousemove (ini hanya akan mempengaruhi penangan ini).
    2. Anda dapat mencegah patch zone.js untuk beberapa acara dengan menggunakan baris kode berikut di polyfills.ts . Ini akan memengaruhi seluruh aplikasi Angular.

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

Jika Anda tertarik pada masalah peningkatan kinerja aplikasi Angular - di sini , di sini , di sini dan di sini - bahan yang berguna tentang ini.

Ringkasan


Sekarang Anda telah membaca artikel ini, 5 alat baru akan muncul di gudang pengembang Angular Anda yang dengannya Anda dapat memecahkan beberapa masalah umum. Kami harap tips yang Anda temukan di sini membantu Anda menghemat waktu.

Pembaca yang budiman! Apakah Anda tahu sesuatu yang membantu Anda menghemat waktu saat mengembangkan aplikasi Angular?

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


All Articles