使用微模板创建组件

大家好
在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方法中丰富数据结构的定义。 在这里,初始化模板后,我们使用模板更新defaultColumns模型。

在标记中,您应注意以下几行-

 <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-container和我们的libProvidePropertyDefValue指令定义模板。

这里最重要的是
“让元素; id:“ id”»

其中element是模板的范围,该范围等于具有列表中数据的对象,
id是微模板的标识符。

现在,我想返回到ProvidePropertyDefValue指令,返回到ngOnInit方法

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

您可以按照示例所示放置微模板,并在指令中“清理”它们,或将其定义完全转移到lib-card-list组件内,因此标记将如下所示:

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

这里的一切都很基础,唯一要考虑的是:
提供者:[{提供:别名,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) => { //       }) 

可以通过mat-form-field来实现过滤,并且类似于在分页期间切换页面,可以更新数据。

仅此而已。 我强烈建议您定期查看角度 /材质库的源代码,我认为这是一个很好的机会,可以增强您在创建灵活而高效的组件方面的知识。 谢谢您的关注。

Source: https://habr.com/ru/post/zh-CN454804/


All Articles