Crie seu componente com micro-modelos

Olá pessoal.
Todos que escreveram sobre a estrutura Angular de alguma forma se depararam (ou até trabalharam) com a biblioteca de Materiais Angular . Essa é uma biblioteca de componentes muito bem escrita, com estilo flexível, implementada através da capacidade de criar vários temas para o seu aplicativo, com um grande conjunto de componentes para todas as ocasiões.

No meu trabalho diário, nenhum projeto pode ficar sem ele.

Mas, além de todas as vantagens da flexibilidade desta biblioteca, também é possível aproveitar a experiência dos criadores ao escrever seus próprios componentes, e este é para mim o melhor manual de desenvolvimento de melhores práticas no Angular .

Neste artigo, quero compartilhar com você como você pode implementar a abordagem com um modelo complexo implementado no módulo MatTableModule .

Como exemplo, quero mostrar como fazer uma lista de cartões com a capacidade de adicionar paginação e filtros e, como base, adotamos o modelo de modelo do componente MatTable.

Modelo ( origem ):

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

Depois de estudar o modelo, fica claro que indicamos nas marcações ng-container a marcação para uma coluna específica da tabela, mas como isso funciona? Foi essa pergunta que fiz quando vi esse design, em parte devido ao fato de não trabalhar com componentes dinâmicos. E então, vamos começar (código fonte) .

Estrutura


Um conjunto de entidades que precisamos criar. Este diagrama de blocos ilustra sua interação.

imagem

Primeiro passo


Precisamos de um serviço para registrar nossos micro-modelos.

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

Segundo passo


Crie uma diretiva para registrar modelos:

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

Passo três


Crie um componente:

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

Uma pequena explicação, o principal trabalho do componente é enriquecer a definição da estrutura de dados no método ngAfterViewInit . Aqui, depois de inicializar os modelos, atualizamos os modelos defaultColumns com modelos.

Na marcação, você pode prestar atenção às seguintes linhas -

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

aqui, um recurso é usado para passar o escopo (como em AngularJS) para a marcação. Isso torna conveniente declarar uma variável em nossos micromodelos por meio da construção let-my-var na qual os dados estarão.

Use


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

Inicializando nosso componente novo e passando parâmetros para ele.

Definição de modelo via ng-container e nossa diretiva libProvidePropertyDefValue .

A coisa mais importante aqui é
"Let elemento; id: 'id' »

onde elemento é o escopo do modelo que é igual ao objeto com os dados da lista,
id é o identificador do micro-modelo.

Agora, quero retornar à diretiva deliverPropertyDefValue , ao método ngOnInit

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

Você pode colocar micro-modelos como mostrado no exemplo e "limpá-los" na diretiva ou transferir completamente sua definição dentro do componente lib-card-list , portanto a marcação ficará assim:

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

Objetivamente - o segundo caso de uso é mais produtivo.

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

Tudo é bastante elementar aqui, a única coisa a considerar é:
fornecedores: [{forneça: Alias, useExisting: forwardRef (() => AppComponent)}]
Esse design é necessário para vincular o modelo e o componente que os utiliza.

Em nosso serviço, o construtor receberá uma instância do componente AppComponent do injetor.

Opcional


Neste exemplo, vimos como criar um componente, para uso repetido em seus projetos, para o qual você pode transferir modelos diferentes com dados; esses modelos podem definitivamente ser qualquer coisa.

Como melhorar?


Você pode adicionar paginação a partir de Material angular e filtragem.

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

A filtragem pode ser implementada através de campos de formulário e, da mesma forma que a troca de páginas durante a paginação, os dados são atualizados.

Só isso. Eu recomendo olhar periodicamente para o código fonte da biblioteca angular / material, na minha opinião, esta é uma boa oportunidade para aprimorar seu conhecimento na criação de componentes flexíveis e produtivos. Obrigado pela atenção.

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


All Articles