قم بإنشاء المكون الخاص بك باستخدام قوالب صغيرة

مرحبا بالجميع.
كل من كتب على إطار Angular صادف (أو حتى عمل) مكتبة Angular Material . هذه مكتبة مكونات مكتوبة جيدًا وقادرة على التصميم المرن ، ويتم تنفيذها من خلال القدرة على إنشاء سمات متعددة للتطبيق الخاص بك ، مع مجموعة كبيرة من المكونات لجميع المناسبات.

في عملي اليومي ، لا يوجد مشروع واحد يمكنه الاستغناء عنه.

ولكن إلى جانب كل مزايا مرونة هذه المكتبة ، يمكن أيضًا الاستفادة من خبرة المبدعين في كتابة مكوناتهم الخاصة ، وهذا هو أفضل دليل عن تطوير أفضل الممارسات على Angular .

في هذه المقالة ، أود أن أشاطركم كيف يمكنك تنفيذ النهج مع قالب معقد يتم تنفيذه في الوحدة النمطية MatTableModule .

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

قالب ( المصدر ):

<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> 

بعد دراسة القالب ، يصبح من الواضح أننا نشير في علامات ng-container إلى علامة لعمود معين في الجدول ، ولكن كيف يعمل في الداخل؟ كان هذا السؤال الذي طرحته عندما رأيت هذا التصميم ، ويرجع ذلك جزئيًا إلى حقيقة أنني لم أعمل مع المكونات الديناميكية. وهكذا ، لنبدأ (الكود المصدري) .

هيكل


مجموعة من الكيانات التي نحتاج إلى إنشائها. يوضح هذا المخطط كتلة تفاعلهم.

صورة

الخطوة الأولى


نحن بحاجة إلى خدمة لتسجيل القوالب الدقيقة لدينا.

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

الخطوة الثانية


إنشاء توجيه لتسجيل القوالب:

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

الخطوة الثالثة


إنشاء مكون:

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

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

في الترميز ، يمكنك الانتباه إلى الأسطر التالية -

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

هنا يتم استخدام ميزة لتمرير النطاق (كما في AngularJS) إلى العلامات. هذا يجعل من الملائم الإعلان عن متغير في قوالبنا الصغيرة من خلال بنية let-my-var التي ستكمن فيها البيانات.

استخدام


 // 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> 

تهيئة المكون الجديد لدينا ، وتمرير المعلمات إليه.

تعريف القالب عبر حاوية ng وتوجيه libProvidePropertyDefValue الخاص بنا.

الشيء الأكثر أهمية هنا هو
"اسمحوا عنصر ؛ المعرف: "معرف" »

حيث يمثل العنصر نطاق القالب الذي يساوي الكائن مع البيانات من القائمة ،
المعرف هو معرف القالب الصغير.

الآن أريد العودة إلى التوجيه providePropertyDefValue ، إلى طريقة ngOnInit

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

يمكنك وضع قوالب مصغرة كما هو موضح في المثال ، و "تنظيفها" في التوجيه ، أو نقل تعريفها بالكامل داخل مكون قائمة بطاقة lib ، وبالتالي ستبدو العلامات كما يلي:

 <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> 

موضوعيا - حالة الاستخدام الثانية هي أكثر إنتاجية.

 @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' } ]); } 

كل شيء أساسي هنا ، الشيء الوحيد الذي يجب مراعاته هو:
الموفرين: [{provide: الاسم المستعار ، useExisting: forwardRef (() => AppComponent)}]
هذا التصميم ضروري لربط القالب والمكون الذي يستخدمه.

في خدمتنا ، سيتلقى المنشئ مثيل مكون AppComponent من الحاقن.

بالإضافة إلى ذلك


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

كيف تتحسن؟


يمكنك إضافة ترقيم الصفحات من الزاوي المواد والتصفية.

 // 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) => { //       }) 

يمكن تنفيذ التصفية من خلال حقل نموذج حصيرة ، وعلى غرار تبديل الصفحات أثناء ترقيم الصفحات ، تحديث البيانات.

هذا كل شيء. أوصي بشدة بالبحث بشكل دوري في الكود المصدري للمكتبة الزاوية / المادية ، في رأيي ، هذه فرصة جيدة لتعزيز معرفتك في إنشاء مكونات مرنة وإنتاجية. شكرا لاهتمامكم

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


All Articles