Reusable DataTable with Material CDK
How many times did you need to make a DataTable to your application? And how many times did you need to apply many table configurations? Some months ago I have just changed my job and it was exactly at the same time as they were having a redesign discussion. So we decided to create our own design system.
We cannot stop our delivery, that was an on-production application, but we need to start to deliver some components from the new layout. I was really worried about some layout inconsistencies and I decided to make our datatable. After analyzing some mockups behaviors I could define a roadmap to make the datatable easy to use and generic.
The basic behavior definition
- datasource: It must be an observable to react to events. This observable could be a response from an API — I wrote about Observables in this post.
- headers: The headers could map each object from our datasource by key. If you need to get some nested value from the object, you need to map it in the stream. We will talk about it later;
- translation key: In a real-world application we need to translate the content, we use ngx-translate to do it. This translation key works on concatenating with each header key to translate it;
Creating the Component
I created a repository at my Github for this post. It is very simple, just create a new angular/cli project and add material, ngx-translate and Storybook. I wrote a post about how Storybook can help us to deliver faster components, check it out. You can see the commits to follow the progress.
In my application, I decided to start with the style, because the target was delivery a new design system and it was easy to apply in other pages. By now I will start to make the table logic and populating itself.
We can start just creating a table component with material schematics/cli
ng g @angular/material:table design-system/componentes/table — changeDetection OnPush
It will give us some files, and we will create a Storybook story to this component… yes, I always use storybook.
After removing some codes from table-tadasource.ts, we have a very simple table:
This table use material design and we want to use just the material CDK, so we will remove the mat-* references. It will remove all style from the table and you can apply the layout from your application.
To make this table generic we need to apply the inputs to receive the properties and parse their values to populate the table. We will set as input the dataSource, headers, and translationKey properties to make it possible.
We also define a property to keep the translatedHeaders updated and prevent dirty checking on the template. The translatedHeaders is a Hash of each header as key and the translationKey.header as value.
The template will integrate the headers and get by key the translation from the translatedHeaders and the element value from the row.
Making Column Customized
The biggest problems to make the table customized was about some behaviors in the columns. In my project, we always need to add a status icon, manipulate date, join object info, and etc…
After some days thinking and trying some approaches of how to solve this problem I tried to use pipes to manipulate it.
The strategy is to define an object as same as translatedHeaders, with key as header and the value is an instance of the pipe you need to apply if you need to apply it.
The customColumns will receive a configuration with the name of the column will have a pipe to make it customized.
The CustomColumnComponent will render the row and make the validation, it could be inside the template, but to make it readable I prefer to separate it.
In this component we will apply the pipe transform to render the pipe, will get the value to send to the pipe and the defined arguments.
At the TableComponent we just check if we have a custom configuration for the current column and apply if it exists.
Advanced behavior
Ok so, the basic behavior is that we created a static table, but we need to interact with it. We can select the values or even add a menu for actions on that, just making some adjusts. In the next post, I will explain more about it.