Buat komponen Anda dengan template mikro

Halo semuanya.
Setiap orang yang menulis tentang kerangka Angular entah bagaimana menemukan (atau bahkan bekerja) dengan perpustakaan Bahan Angular . Ini adalah pustaka komponen yang ditulis dengan sangat baik yang mampu menata gaya yang fleksibel, yang diimplementasikan melalui kemampuan untuk membuat berbagai tema untuk aplikasi Anda, dengan sejumlah besar komponen untuk semua kesempatan.

Dalam pekerjaan sehari-hari saya, tidak ada satu proyek pun yang dapat melakukannya tanpanya.

Tetapi di samping semua keuntungan dari fleksibilitas perpustakaan ini, juga dimungkinkan untuk memanfaatkan pengalaman para pembuat dalam menulis komponen mereka sendiri, dan bagi saya ini adalah panduan terbaik tentang pengembangan praktik terbaik di Angular .

Pada artikel ini saya ingin berbagi dengan Anda bagaimana Anda dapat menerapkan pendekatan dengan templat kompleks yang diimplementasikan dalam modul MatTableModule .

Sebagai contoh, saya ingin menunjukkan cara membuat daftar kartu dengan kemampuan untuk menambahkan pagination dan filter , dan sebagai dasar kita mengambil model model komponen MatTable.

Templat ( sumber ):

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> <ng-container matColumnDef="position"> <th mat-header-cell *matHeaderCellDef> No. </th> <td mat-cell *matCellDef="let element"> {{element.position}} </td> </ng-container> <ng-container matColumnDef="name"> <th mat-header-cell *matHeaderCellDef> Name </th> <td mat-cell *matCellDef="let element"> {{element.name}} </td> </ng-container> <ng-container matColumnDef="weight"> <th mat-header-cell *matHeaderCellDef> Weight </th> <td mat-cell *matCellDef="let element"> {{element.weight}} </td> </ng-container> <ng-container matColumnDef="symbol"> <th mat-header-cell *matHeaderCellDef> Symbol </th> <td mat-cell *matCellDef="let element"> {{element.symbol}} </td> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> </table> 

Setelah mempelajari templat, menjadi jelas bahwa kami mengindikasikan dalam wadah ng menandai markup untuk kolom tabel tertentu, tetapi bagaimana cara kerjanya di dalam? Ini adalah pertanyaan yang saya tanyakan ketika saya melihat desain ini, sebagian karena fakta bahwa saya tidak bekerja dengan komponen dinamis. Jadi, mari kita mulai (kode sumber) .

Struktur


Seperangkat entitas yang perlu kita buat. Diagram blok ini menggambarkan interaksi mereka.

gambar

Langkah pertama


Kami membutuhkan layanan untuk mendaftarkan template mikro kami.

 @Injectable() export class RegisterPropertyDef<T> { //        Map //    -  ,     //          //         private store = new Map<ComponentInstance, Map<string, TemplateRef<T>>>(); setTemplateById(cmp: ComponentInstance, id: string, template: TemplateRef<any>): void { const state = this.store.get(cmp) || new Map(); state.set(id, template); this.store.set(cmp, state); } getTemplate(cmp: ComponentInstance, id: string): TemplateRef<T> { return this.store.get(cmp).get(id); } } 

Langkah kedua


Buat arahan untuk mendaftarkan template:

 @Directive({ selector: '[providePropertyDefValue]' }) export class ProvidePropertyDefValueDirective<T> implements OnInit { @Input() providePropertyDefValueId: string; constructor( private container: ViewContainerRef, private template: TemplateRef<any>, //       private registerPropertyDefService: RegisterPropertyDefService<any>, //    @Optional() private parent: Alias<T[]> //             ) {} ngOnInit(): void { this.container.clear(); //    ,    this.registerPropertyDefService.setTemplateById( this.parent as ComponentInstance, this.providePropertyDefValueId, this.template ); } } 

Langkah ketiga


Buat komponen:

 @Component({ selector: 'lib-card-list', template: ` <mat-card *ngFor="let source of sources"> <ul> <li *ngFor="let key of displayedColumns"> <span>{{ findColumnByKey(key)?.label }}</span> <span> <ng-container [ngTemplateOutlet]="findColumnByKey(key)?.template || default" [ngTemplateOutletContext]="{ $implicit: source }" ></ng-container> </span> </li> </ul> </mat-card> <ng-template #default></ng-template> `, styles: [ 'mat-card { margin: 10px; }' ] }) export class CardListComponent<T> implements OnInit, AfterViewInit { @Input() defaultColumns: DefaultColumn[]; @Input() source$: Observable<T[]>; displayedColumns = []; sources: T[] = []; constructor(private readonly registerPropertyDefService: RegisterPropertyDefService<T>, private readonly parent: Alias<T[]>) { } ngOnInit() { this.source$.subscribe((data: T[]) => this.sources = data); this.displayedColumns = this.defaultColumns.map(c => c.id); } findColumnByKey(key: string): DefaultColumn { return this.defaultColumns.find(column => column.id === key); } ngAfterViewInit(): void { this.defaultColumns = this.defaultColumns.map(column => Object.assign(column, { template: this.registerPropertyDefService.getTemplate(this.parent as ComponentInstance, column.id) }) ); } } 

Sedikit penjelasan, pekerjaan utama dari komponen ini adalah untuk memperkaya definisi struktur data dalam metode ngAfterViewInit . Di sini, setelah menginisialisasi templat, kami memperbarui model Kolom default dengan templat.

Di markup, Anda bisa memperhatikan baris berikut -

 <ng-container [ngTemplateOutlet]="findColumnByKey(key)?.template || default" [ngTemplateOutletContext]="{ $implicit: source }"></ng-container> 

di sini fitur digunakan untuk melewatkan lingkup (seperti dalam AngularJS) ke markup. Ini memudahkan untuk mendeklarasikan variabel dalam templat mikro kami melalui konstruk let-my-var tempat data berada.

Gunakan


 // app.component.html <lib-card-list [defaultColumns]="defaultColumns" [source$]="sources$"></lib-card-list> <ng-container *libProvidePropertyDefValue="let element; id: 'id'"> {{ element.id }} </ng-container> <ng-container *libProvidePropertyDefValue="let element; id: 'title'"> {{ element.title }} </ng-container> 

Menginisialisasi komponen baru kami, dan meneruskan parameter ke sana.

Definisi template melalui ng-container dan arahan libProvidePropertyDefValue kami.

Yang paling penting di sini adalah
"Biarkan elemen; id: 'id' ยป

di mana elemen adalah lingkup template yang sama dengan objek dengan data dari daftar,
id adalah pengidentifikasi template-mikro.

Sekarang saya ingin kembali ke direktif offerPropertyDefValue , ke metode ngOnInit

  ngOnInit(): void { this.container.clear(); ... } 

Anda dapat menempatkan template mikro seperti yang ditunjukkan dalam contoh, dan "membersihkannya" dalam arahan, atau mentransfer definisi mereka sepenuhnya di dalam komponen daftar kartu-lib , oleh karena itu markupnya akan terlihat seperti ini:

 <lib-card-list [defaultColumns]="defaultColumns" [source$]="sources$"> <ng-container *libProvidePropertyDefValue="let element; id: 'id'"> {{ element.id }} </ng-container> <ng-container *libProvidePropertyDefValue="let element; id: 'title'"> {{ element.title }} </ng-container> </lib-card-list> 

Secara obyektif - use case kedua lebih produktif.

 @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [{ provide: Alias, useExisting: forwardRef(() => AppComponent) }] }) export class AppComponent extends Alias<any> { title = 'card-list-example'; defaultColumns: DefaultColumn[] = [ { id: 'id', label: 'ID' }, { id: 'title', label: 'Title' } ]; sources$ = of([ { id: 1, title: 'Hello' }, { id: 2, title: 'World' } ]); } 

Semuanya sangat mendasar di sini, satu-satunya hal yang perlu dipertimbangkan adalah:
penyedia: [{sediakan: Alias, useExisting: forwardRef (() => AppComponent)}]
Desain ini diperlukan untuk menghubungkan templat dan komponen yang menggunakannya.

Dalam layanan kami, konstruktor akan menerima komponen komponen AppComponent dari injector.

Opsional


Dalam contoh ini, kami melihat cara membuat komponen, untuk penggunaan berulang dalam proyek Anda, di mana Anda dapat mentransfer template berbeda dengan data, template ini pasti bisa apa saja.

Bagaimana cara meningkatkannya?


Anda dapat menambahkan pagination dari Material Sudut dan memfilter.

 // card-list.component.html <mat-paginator [pageSize]="5"showFirstLastButton></mat-paginator> 

 // card-list.component.ts @ViewChild(MatPaginator) paginator: MatPaginator; this.paginator.initialized.subscribe(() => { //     }); this.paginator.page.subscribe((pageEvent: PageEvent) => { //       }) 

Pemfilteran dapat diimplementasikan melalui mat-form-field dan, mirip dengan berpindah halaman saat pagination, memperbarui data.

Itu saja. Saya sangat merekomendasikan secara berkala melihat ke kode sumber perpustakaan sudut / materi, menurut saya ini adalah kesempatan yang baik untuk meningkatkan pengetahuan Anda dalam menciptakan komponen yang fleksibel dan produktif. Terima kasih atas perhatian anda

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


All Articles