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.

Première étape
Nous avons besoin d'un service pour enregistrer nos micro-modèles.
@Injectable() export class RegisterPropertyDef<T> {
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();
É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>
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.