Smart Mat-Table part 2 : Dynamic columns
This is the second article of a small series in order to created highly reusable and customizable mat-table components while reducing boilerplate.
In this article we will created one HTML template that will be used by all the simple columns in order to reduce boilerplate code in HTML (when uising mat-table).
The code showed on this article is based on the first part of the serie, but it can be understood without reading the first part.
Introduction
Let’s say we want to display a table of users (with firstname, lastname, mail and job). One of the problems of the mat-table component is that it needs a lot of boilerplate code in the HTML, all the columns must be defined.
For example to handle the user firstname and lastname columns you will need to defined the following HTML at least.
In this case, we only show the firstname and lastname of the user in a span without any style. Both columns definitions are the same which result in lot of boilerplate code.
It would be nice if we could tell the table to show the firstname without having to explicitly defined this column in the HTML.
Generate columns
For the mat-table to work it needs a matColumnDef to render the column. We can generate those columns definition in a loop. So we need to get a list of dynamic columns and then loop on it to generate the columns definitions. If we set our column’s name like our entity’s property we will be able to simply access the property of the object from the column name.
Let’s see this in action.
Here is our User inteface :
export interface User {
firstname: string;
lastname: string;
mail: string;
job: string;
}
We want to generate the firstname and lastname columns automatically, here is how we can do it.
The datasource and columns are input to defined what are the data and which columns need to be displayed (see part 1). Here, the datasource is a simple list of users. We define the columns input as following to display all columns.
columns: string[] = ['firstname', 'lastname', 'mail', 'job'];
In the component we defined the list of dynamic columns.
dynamicColumns: string[] = ['firstname', 'lastname'];
The important part is under the Dynamic columns comment (in HTML).
<ng-container *ngFor="let dynamicColumn of dynamicColumns" [matColumnDef]="dynamicColumn">
This line loops through the dynamic columns and create all the matColumnDef for thoses columns. This tell mat-table that the firstname and lastname columns exist.
Then we have to define the template for the header and the cells.
In the column header we simply display the property name (which is firstname or lastname).
<mat-header-cell *matHeaderCellDef>
<p class="text-capitalize-first">{{dynamicColumn}}</p>
</mat-header-cell>
In the cells we retrieve the user firstname or lastname with the colums name.
<mat-cell *matCellDef="let user">
<span>{{user[dynamicColumn]}}</span>
</mat-cell>
This is why it is important to call the dynamic columns with the user property name, it simplify the access of the property.
Et voilà ! 🍾 The firstname and lastname column are now dynamically generated and you can simply add a new columns.
It’s a good start but we can improve it, there is some problems :
- if we add a new dynamic columns we will need to add it in the list of dynamic columns. Can’t we calculate the dynamic columns based on static columns to avoid the management of dynamics ?
- we should improve the typing of the dynamic columns as the strings must be keys of the User
- we can implement this logic in a Abstract class, make it generic and all the table components will extends it.
Let’s do this !
Improvements
Calculate dynamic columns
To calculate dynamic columns we can retrieve them by checking the static columns.
In our example, our static columns are mail and job (there are manually defined in the HTML).
staticColumns = ['mail', 'job']
When we get the list of columns to display, we can compare them with the list of static columns to get the list of dynamic columns.
Here is a simple function that will do the work.
We can now set the dynamicColumns attribute of the DynamicColumnsComponent with the value return by this function. In our example this function will return the firstname and lastname as they are not in the staticColumns array
Improve typing
To improve typing we can simply use the key of type. We will defined a type to list all the static columns (because we no longer define the list of dynamic columns) and columns to display.
Let’s create the TableColumn type.
export type TableColumn<T> = keyof T | string;
We make it generic to work with any classes or interfaces.
We added a union string ( | string ) because there is some case when a static column is not an attribute of the entity. For example you may have action button (to edit the user for example) in you table. The column name will be ‘action’ and it is not a property of the user.
Basically this type helps with the autocompletion but it also let the developper defined any static columns (it doesn’t have to be a property of the User).
Create an abstract class
Here is the abstract class that
- define the datasource and columns
- calculate the dynamic columns when the columns to display changes
- uses the TableColumn type
This class can be easily reused in another table component.
And that the final version of the dynamic columns where the child component only define the template and the list of static column.
The list of static column must match the columns defined in the HTML otherwise the mat-table will throw some error (like duplicate columns).
Here is a working example using the final version.
Possible improvements
For advanced dynamic columns, you can create a component that handle different type of object (like string, number, date, User…) and use it in the dynamic column cell.
And for the column header name you can use a pipe or a map object to change the columns name to something else.
Conclusion
Here is how I managed to generate dynamic columns and reduce boilerplate code using mat-table.
In part 3 we will see how to improve customization by being able to inject custom columns in table from parent.
Here is the full github repository of the series. This part is the TableWithDynamicColumnsComponent → https://github.com/BobBDE/smart-mat-table
Thank you for reading :) !