Créez votre composant avec des micro-modèles

Bonjour Ă  tous.
Tous ceux qui ont écrit sur le framework Angular ont rencontré (ou même travaillé) la bibliothèque Angular Material . Il s'agit d'une bibliothèque de composants très bien écrite, capable d'un style flexible, qui est implémentée grâce à la possibilité de créer divers thèmes pour votre application, avec un large ensemble de composants pour toutes les occasions.

Dans mon travail quotidien, aucun projet ne peut s'en passer.

Mais en plus de tous les avantages de la flexibilité de cette bibliothèque, il est également possible de s'appuyer sur l'expérience des créateurs dans l'écriture de leurs propres composants, et c'est pour moi le meilleur manuel sur le développement des meilleures pratiques sur Angular .

Dans cet article, je veux partager avec vous comment vous pouvez implémenter l'approche avec un modèle complexe implémenté dans le module MatTableModule .

À titre d'exemple, je veux montrer comment faire une liste de cartes avec la possibilité d'ajouter la pagination et les filtres , et comme base, nous prenons le modèle de modèle du composant MatTable.

Modèle ( source ):

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

Après avoir étudié le modèle, il devient clair que nous indiquons dans les balises ng-container le balisage pour une colonne spécifique du tableau, mais comment cela fonctionne-t-il à l'intérieur? C'est cette question que j'ai posée lorsque j'ai vu cette conception, en partie parce que je n'ai pas travaillé avec des composants dynamiques. Et donc, commençons (code source) .

La structure


Un ensemble d'entités que nous devons créer. Ce schéma fonctionnel illustre leur interaction.

image

Première étape


Nous avons besoin d'un service pour enregistrer nos micro-modèles.

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

Deuxième étape


Créez une directive pour l'enregistrement des modèles:

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

Étape trois


Créez un composant:

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

Une petite explication, le travail principal du composant est d'enrichir la définition de la structure des données dans la méthode ngAfterViewInit . Ici, après avoir initialisé les modèles, nous mettons à jour les modèles defaultColumns avec des modèles.

Dans le balisage, vous pouvez faire attention aux lignes suivantes -

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

ici, une fonctionnalité est utilisée pour passer la portée (comme dans AngularJS) au balisage. Cela facilite la déclaration d'une variable dans nos micro-modèles via la construction let-my-var dans laquelle les données se trouveront.

Utiliser


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

Initialiser notre nouveau composant et lui passer des paramètres.

Définition de modèle via ng-container et notre directive libProvidePropertyDefValue .

La chose la plus importante ici est
"Let élément; id: 'id' »

où élément est la portée du modèle qui est égale à l'objet avec les données de la liste,
id est l'identifiant du micro-modèle.

Maintenant, je veux revenir à la directive providePropertyDefValue , à la méthode ngOnInit

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

Vous pouvez placer des micro-modèles comme indiqué dans l'exemple et les «nettoyer» dans la directive, ou transférer complètement leur définition à l'intérieur du composant lib-card-list , donc le balisage ressemblera à ceci:

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

Objectivement - le deuxième cas d'utilisation est plus productif.

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

Tout est assez élémentaire ici, la seule chose à considérer est:
fournisseurs: [{fournir: Alias, useExisting: forwardRef (() => AppComponent)}]
Cette conception est nécessaire pour lier le modèle et le composant qui les utilise.

Dans notre service, le constructeur recevra une instance du composant AppComponent de l'injecteur.

En option


Dans cet exemple, nous avons examiné comment créer un composant, pour une utilisation répétée dans vos projets, pour lequel vous pouvez transférer différents modèles avec des données, ces modèles peuvent certainement être n'importe quoi.

Comment s'améliorer?


Vous pouvez ajouter la pagination du matériau angulaire et le filtrage.

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

Le filtrage peut être implémenté via mat-form-field et, comme pour changer de page pendant la pagination, mettre à jour les données.

C’est tout. Je recommande fortement de consulter périodiquement le code source de la bibliothèque angulaire / matérielle, à mon avis, c'est une bonne occasion d'améliorer vos connaissances dans la création de composants flexibles et productifs. Merci de votre attention.

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


All Articles