大家好
在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> {
第二步
创建用于注册模板的指令:
@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();
第三步
创建一个组件:
@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>
可以通过mat-form-field来实现过滤,并且类似于在分页期间切换页面,可以更新数据。
仅此而已。 我强烈建议您定期查看
角度 /材质库的源代码,我认为这是一个很好的机会,可以增强您在创建灵活而高效的组件方面的知识。 谢谢您的关注。