In this post, we are going to go through a complete example of how to use the Angular Material Data Table.
We are going to cover many of the most common use cases that revolve around the Angular Material Data Table component, such as: server-side pagination, sorting, and filtering.
This is a step-by-step tutorial, so I invite you to code along as we are going to start with a simple initial scenario. We will then progressively add features one by one and explain everything along the way (including gotchas).
We will learn in detail all about the reactive design principles involved in the design of the Angular Material Data Table and an Angular CDK Data Source.
The end result of this post will be:
- a complete example of how to implement an Angular Material Data Table with server-side pagination, sorting and filtering using a custom CDK Data Source
- a running example available on Github, which includes a small backend Express server that serves the paginated data
Table Of Contents
In this post, we will cover the following topics:
- The Angular Material Data Table - not only for Material Design
- The Material Data Table Reactive Design
- The Material Paginator and Server-side Pagination
- Sortable Headers and Server-side Sorting
- Server-side Filtering with Material Input Box
- A Loading Indicator
- A Custom Angular Material CDK Data Source
- Source Code (on Github) with the complete example
So without further ado, let's get started with our Material Data Table Guided Tour!
Importing Angular Material modules
In order to run our example, let's first import all the Angular Material modules that we will need:
Here is a breakdown of the contents of each Material module:
- MatInputModule: this contains the components and directives for adding Material design Input Boxes to our application (needed for the search input box)
- MatTableModule: this is the core data table module, which includes the
mat-tablecomponent and many related components and directives
- MatPaginatorModule: this is a generic pagination module, that can be used to paginate data in general. This module can also be used separately from the Data table, for example for implementing Detail pagination logic in a Master-Detail setup
- MatSortModule: this is an optional module that allows adding sortable headers to a data table
- MatProgressSpinnerModule: this module includes the progress indicator component that we will be using to indicate that data is being loaded from the backend
Introduction to the Angular Material Data Table
The Material Data Table component is a generic component for displaying tabulated data. Although we can easily give it a Material Design look and feel, this is actually not mandatory.
In fact, we can give the Angular Material Data table an alternative UI design if needed. To see that this is so, let's start by creating a Data Table where the table cells are just plain divs with no custom CSS applied.
This data table will display a list of course lessons, and has 3 columns (sequence number, description and duration):
Material Data Table Column Definitions
As we can see, this table defines 3 columns, each inside its own
ng-container element. The
ng-container element will NOT be rendered to the screen (see this post for more details), but it will provide an element for applying the
matColumnDef directive uniquely identifies a given column with a key: seqNo, description or duration. Inside the
ng-container element, we will have all the configuration for a given column.
Notice that the order of the
ng-containerelements does NOT determine the column visual order
The Material Data Table Auxiliary Definition Directives
The Material Data Table has a series of auxiliary structural directives (applied using the
*directiveName syntax) that allow us to mark certain template sections has having a certain role in the overall data table design.
These directives always end with the
Def postfix, and they are used to assign a role to a template section. The first two directives that we will cover are
Inside of each
ng-container with a given column definition, there are a couple of configuration elements:
- we have the template that defines how to display the header of a given column, identified via the
- we also have another template that defines how to display the data cells of a given column, using the
These two structural directives only identify which template elements have a given role (cell template, header template), but they do not attach any styling to those elements.
For example, in this case,
matHeaderCellDef are being applied to plain divs with no styling, so this is why this table does not have a Material design yet.
Applying a Material Design to the Data Table
Let's now see what it would take to give this Data Table a Material Look and Feel. For that, we will use a couple of built-in components in our header and cell template definitions:
This template is almost the same as the one we saw before, but now we are using the
mat-cell components inside our column definition instead of plain divs.
Using these components, lets now have a look at what the Data Table looks like with this new Material Design:
Notice that the table already has some data! We will get to the data source in a moment, right now let's continue exploring the rest of the template.
The data cell template has access to the data that is being displayed. In this case, our data table is displaying a list of lessons, so the lesson object in each row is accessible via the
let lesson syntax, and can be used in the template just like any component variable.
mat-header-row component and the
This combination of related component / directive works in the following way:
matHeaderRowDefidentifies a configuration element for the table header row, but it does not apply any styling to the element
mat-header-rowon the other hand applies some minimal Material stying
matHeaderRowDef directive also defines in which order the columns should be displayed. In our case, the directive expression is pointing to a component variable named
Here is what the
displayedColumns component variable will look like:
The values of this array are the column keys, which need to be identical to the names of the
ng-container column sections (specified via the
Note: It's this array that determines the visual order of the columns!
mat-row component and the
This component / directive pair also works in a similar way to what we have seen in previous cases:
matRowDefidentifies which element inside
mat-tableprovides configuration for how a data row should look like, without providing any specific styling
- on the other hand,
mat-rowwill provide some Material stying to the data row
mat-row, we also have a variable exported that we have named
row, containing the data of a given data row, and we have to specify the
columns property, which contains the order on which the data cells should be defined.
Interacting with a given table data row
We can even use the element identified by the
matRowDef directive to interact with a given data row. For example, this is how we can detect if a given data row was clicked:
When a row is clicked, we will call the
onRowClicked() component method, that will then log the row data to the console:
If we now click on the first row of our data table, here is what the result will look like on the console:
As we can see the data for the first row is being printed to the console, as expected! But where is this data coming from?
To answer that, let's then talk about the data source that is linked to this data table, and go over the Material Data Table reactive design.
Data Sources and the Data Table Reactive Design
The data table that we have been presenting receives the data that it displays from a Data Source that implements an Observable-based API and follows common reactive design principles.
This means for example that the data table component does not know where the data is coming from. The data could be coming for example from the backend, or from a client-side cache, but that is transparent to the Data table.
The Data table simply subscribes to an Observable provided by the Data Source. When that Observable emits a new value, it will contain a list of lessons that then get's displayed in the data table.
Data Table core design principles
With this Observable-based API, not only the Data table does not know where the data is coming from, but the data table also does not know what triggered the arrival of new data.
Here are some possible causes for the emission of new data:
- the data table is initially displayed
- the user clicks on a paginator button
- the user sorts the data by clicking on a sortable header
- the user types a search using an input box
Again, the Data Table has no information about exactly which event caused new data to arrive, which allows the Data Table components and directives to focus only on displaying the data, and not fetching it.
Let's then see how can we implement such a reactive data source.
Why not use
In this example, we will not be using the built-in
MatTableDataSource because its designed for filtering, sorting and pagination of a client-side data array.
In our case, all the filtering, sorting and pagination will be happening on the server, so we will be building our own Angular CDK reactive data source from first principles.
Fetching Data from the backend
In order to fetch data from the backend, our custom Data Source is going to be using the
LessonsService. This is a standard Observable-based stateless singleton service that is built internally using the Angular HTTP Client.
Let's have a look at this service, and break down how its implemented:
Breaking down the
As we can see, this service is completely stateless, and every method forwards calls to the backend using the HTTP client, and returns an Observable to the caller.
Our REST API is available in URLs under the
/api directory, and multiple services are available (here is the complete implementation).
In this snippet, we are just showing the
findLessons() method, that allows to obtain one filtered and sorted page of lessons data for a given course.
Here are the arguments that we can pass to this function:
- courseId: This identifies a given course, for which we want to retrieve a page of lessons
- filter: This is a search string that will help us filter the results. If we pass the empty string '' it means that no filtering is done on the server
- sortOrder: our backend allows us to sort based on the
seqNocolumn, and with this parameter, we can specify is the sort order is ascending (which is the default
ascvalue), or descending by passing the value
- pageNumber: With the results filtered and sorted, we are going to specify which page of that full list of results we need. The default is to return the first page (with index 0)
- pageSize: this specifies the page size, which defaults to a maximum of 3 elements
With this arguments, the
loadLessons() method will then build an HTTP GET call to the backend endpoint available at
Here is what an HTTP GET call that fetches the lessons for the first page looks like:
As we can see, we are appending a series of HTTP query parameters to the GET URL using the
HTTPParams fluent API.
loadLessons() method will be the basis of our Data Source, as it will allow us to cover the server pagination, sorting and filtering use cases.
Implementing a Custom Angular CDK Data Source
LessonsService, let's now implement a custom Observable-based Angular CDK Data Source. Here is some initial code, so that we can discuss its Reactive design (the full version is shown in a moment):
Breaking down the design of an Angular CDK Data Source
Has we can see, in order to create a Data Source we need to create a class that implements
DataSource. This means that this class needs to implement a couple of methods:
Note that these methods provide an argument which is a
CollectionViewer, which provides an Observable that emits information about what data is being displayed (the start index and the end index).
We would recommend for now not to focus so much on the
CollectionViewer at this moment, but on something much more important for understanding the whole design: the return value of the
How to implement the DataSource
This method will be called once by the Data Table at table bootstrap time. The Data Table expects this method to return an Observable, and the values of that observable contain the data that the Data Table needs to display.
In this case, this observable will emit a list of Lessons. As the user clicks on the paginator and changes to a new page, this observable will emit a new value with the new lessons page.
We will implement this method by using a subject that is going to be invisible outside this class. That subject (the
lessonsSubject) is going to be emitting the values retrieved from the backend.
lessonsSubject is a
BehaviorSubject, which means its subscribers will always get its latest emitted value (or an initial value), even if they subscribed late (after the value was emitted).
BehaviorSubject is a great way of writing code that works independently of the order that we use to perform asynchronous operations such as: calling the backend, binding the data table to the data source, etc.
For example, in this design, the Data Source is not aware of the data table or at which moment the Data Table will require the data. Because the data table subscribed to the
connect() observable, it will eventually get the data, even if:
- the data is still in transit coming from the HTTP backend
- or if the data was already loaded
Custom Material CDK Data Source - Full Implementation Review
Now that we understand the reactive design of the data source, let's have a look at the complete final implementation and review it step-by-step.
Notice that in this final implementation, we have also included the notion of a loading flag, that we will use to display a spinning loading indicator to the user later on:
Data Source Loading Indicator Implementation Breakdown
Let's start breaking down this code, we will start first with the implementation of the loading indicator. Because this Data Source class has a reactive design, let's implement the loading flag by exposing a boolean observable called
This observable will emit as first value
false (which is defined in the
BehaviorSubject constructor), meaning that no data is loading initially.
loading$ observable is derived using
asObservable() from a subject that is kept private to the data source class. The idea is that only this class knows when data is loading, so only this class can access the subject and emit new values for the loading flag.
connect() method implementation
Let's now focus on the implementation of the connect method:
This method will need to return an Observable that emits the lessons data, but we don't want to expose the internal subject
Exposing the subject would mean yielding control of when and what data gets emitted by the data source, and we want to avoid that. We want to ensure that only this class can emit values for the lessons data.
So we are also going to return an Observable derived from
lessonsSubject using the
asObservable() method. This gives the data table (or any other subscriber) the ability to subscribe to the lessons data observable, without being able to emit values for that same observable.
disconnect() method implementation
Let's now break down the implementation of the disconnect method:
This method is called once by the data table at component destruction time. In this method, we are going to complete any observables that we have created internally in this class, in order to avoid memory leaks.
We are going to complete both the
lessonsSubject and the
loadingSubject, which are then going to trigger the completion of any derived observables.
loadLessons() method implementation
Finally, let's now focus on the implementation of the loadLessons method:
The Data Source exposes this public method named
loadLessons(). This method is going to be called in response to multiple user actions (pagination, sorting, filtering) to load a given data page.
Here is how this method works:
- the first thing that we will do is to report that some data is being loaded, by emitting
loadingSubject, which will cause
loading$to also emit true
LessonsServiceis going to be used to get a data page from the REST backend
- a call to
findLessons()is made, that returns an Observable
- by subscribing to that observable, we trigger an HTTP request
- if the data arrives successfully from the backend, we are going to emit it back to the data table, via the
- for that, we will call
lessonsSubjectwith the lessons data
- the derived lessons observable returned by
connect()will then emit the lessons data to the data table
Handling Backend Errors
Let's now see, still in the
loadLessons() method, how the Data Source handles backend errors, and how the loading indicator is managed:
- if an error in the HTTP request occurs, the Observable returned by
findLessons()will error out
- If that occurs, we are going to catch that error using
catchError()and we are going to return an Observable that emits the empty array using
- we could complementary also use another
MessagesServiceto show a closable error popup to the user
- wether the call to the backend succeeds or fails, we will in both cases have the
finalize()(which works like
And with this last bit, we have completed the review of our custom Data Source!
This version of the data source will support all our use cases: pagination, sorting and filtering. As we can see, the design is all about providing data transparently to the Data Table using an Observable-based API.
Let's now see how we can take this Data Source and plug it into the Data Table.
Linking a Data Source with the Data Table
The Data Table will be displayed as part of the template of a component. Let's write an initial version of that component, that displays the first page of lessons:
This component contains a couple of properties:
displayedColumnsarray defines the visual order of the columns
dataSourceproperty defines an instance of
LessonsDataSource, and that is being passed to
mat-tablevia the template
Breaking down the
ngOnInit method, we are calling the Data Source
loadLessons() method to trigger the loading of the first lessons page. Let's detail what happens as a result of that call:
- The Data Source calls the
LessonsService, which triggers an HTTP request to fetch the data
- The Data Source then emits the data via the
lessonsSubject, which causes the Observable returned by
connect()to emit the lessons page
mat-tableData Table component has subscribed to the
connect()observable and retrieves the new lessons page
- The Data Table then displays the new lessons page, without knowing where the data came from or what triggered its arrival
And with this "glue" component in place, we now have a working Data Table that displays server data!
The problem is that this initial example is always loading only the first page of data, with a page size of 3 and with no search criteria.
Let's use this example as a starting point, and starting adding: a loading indicator, pagination, sorting, and filtering.
Displaying a Material Loading Indicator
In order to display the loading indicator, we are going to be using the
loading$ observable of the Data Source. We will be using the
mat-spinner Material component:
As we can see, we are using the
async pipe and
ngIf to show or hide the material loading indicator. Here is what the table looks like while the data is loading:
We will also be using the loading indicator when transitioning between two data pages using pagination, sorting or filtering.
Adding a Data Table Material Paginator
The Material Paginator component that we will be using is a generic paginator that comes with an Observable-based API. This paginator could be used to paginate anything, and it's not specifically linked to the Data Table.
For example, on a Master-Detail component setup, we could use this paginator to navigate between two detail elements.
This is how the
mat-paginator component can be used in a template:
As we can see, there is nothing in the template linking the paginator with either the Data Source or the Data Table - that connection will be done at the level of the
The paginator only needs to know how many total items are being paginated (via the length property), in order to know how many total pages there are!
Its based on that information (plus the current page index) that the paginator will enable or disable the navigation buttons.
In order to pass that information to the paginator, we are using the
lessonsCount property of a new
How to Link the Material Paginator to the Data Source
Let's now have a look at the
CourseComponent, to see where
course is coming from and how the paginator is linked to the Data Source:
Breaking down the
Let's start with the
course object: as we can see this object is available at component construction time via the router.
This data object was retrieved from the backend at router navigation time using a router Data Resolver (see an example here).
This is a very common design, that ensures that the target navigation screen already has some pre-fetched data ready to display.
We are also loading the first page of data directly in this method (on line 20).
How is the Paginator linked to the Data Source?
We can see in the code above that the link between the paginator and the Data Source is done in the
ngAfterViewInit() method, so let's break it down:
We are using the
AfterViewInit lifecycle hook because we need to make sure that the paginator component queried via
@ViewChild is already available.
The paginator also has an Observable-based API, and it exposes a
page Observable. This observable will emit a new value every time that the user clicks on the paginator navigation buttons or the page size dropdown.
So in order to load new pages in response to a pagination event, all we have to do is to subscribe to this observable, and in response to a pagination event, we are going to make a call to the Data Source
loadLessons() method, by calling
In that call to
loadLessons(), we are going to pass to the Data Source what page index we would like to load, and what page size, and that information is taken directly from the paginator.
Why have we used the
We could also have done the call to the data source from inside a
subscribe() handler, but in this case, we have implemented that call using the pipeable version of the RxJs
do operator called
View the Paginator in Action
And with this in place, we now have a working Material Paginator! Here is what the Material Paginator looks like on the screen, while displaying page 2 of the lessons list:
Let's now continue to add more features to our example, let's add another very commonly needed feature: sortable table headers.
Adding Sortable Material Headers
In order to add sortable headers to our Data Table, we will need to annotate it with the
matSort directive. In this case, we will make only one column in the table sortable, the
Here is what the template with all the multiple sort-related directives looks like:
matSort directive, we are also adding a couple of extra auxiliary sort-related directives to the
- matSortActive: When the data is passed to the Data Table, its usually already sorted. This directive allows us to inform the Data Table that the data is already initally sorted by the
seqNocolumn, so the
seqNocolumn sorting icon will be displayed as an upwards arrow
- matSortDirection: This is a companion directive to
matSortActive, it specifies the direction of the initial sort. In this case, the data is initially sorted by the
seqNocolumn in ascending order, and so the column header will adapt the sorting icon accordingly (screenshot below)
- matSortDisableClear: Sometimes, besides ascending and descending order we might want a third "unsorted" state for the sortable column header, where we can clear the sorting order. In this case, we want to disable that to make sure the
seqNocolumn always shown either the ascending or descending states
This is the sort configuration for the whole data table, but we also need to identify exactly what table headers are sortable!
In our case, only the
seqNo column is sortable, so we are annotating the column header cell with the
And this covers the template changes, let's now have a look at the changes we made to the
CourseComponent in order to enable table header sorting.
Linking the Sortable column header to the Data Source
Just like the case of pagination, the sortable header will expose an Observable that emits values whenever the user clicks on the sortable column header.
MatSort directive then exposes a sort Observable, that can trigger a new page load in the following way:
As we can see, the sort Observable is now being merged with the page observable! Now a new page load will be triggered in two cases:
- when a pagination event occurs
- when a sort event occurs
The sort direction of the
seqNo column is now taken from the sort directive (injected via
@ViewChild()) to the backend.
Notice that after each sort we are also resetting the paginator, by forcing the first page of the sorted data to be displayed.
The Material Sort Header In Action
Here is what the Data Table with sortable headers looks like, after loading the data and clicking the sortable header (triggering a descending sort by
Notice the sort icon on the
At this point, we have server pagination and sorting in place. We are now ready to add the final major feature: server-side filtering.
Adding Server-Side Filtering
In order to implement server-side filtering, the first thing that we need to do is to add a search box to our template.
And because this is the final version, let's then display the complete template with all its features: pagination, sorting and also server-side filtering:
Breaking down the Search Box implementation
As we can see, the only new part in this final template version is the
mat-input-container, containing the Material Input box where the user types the search query.
This input box follows a common pattern found in the Material library: The
mat-input-container is wrapping a plain HTML input and projecting it.
This gives us full access to all standard input properties including for example all the Accessibility-related properties. This also gives us compatibility with Angular Forms, as we can apply Form directives directly in the input HTML element.
Read more about how to build a similar component in this post: Angular ng-content and Content Projection: The Complete Guide.
Notice that there is not even an event handler attached to this input box ! Let's then have a look at the component and see how this works.
Final Component with Server Pagination, Sorting and Filtering
This is the final version of
CourseComponent with all features included:
Let's then focus on breaking down the server filtering part.
Getting a reference to the Search Input Box
We can see that we have injected a DOM reference to the
<input> element using
@ViewChild('input'). Notice that this time around, the injection mechanism gave us a reference to a DOM element and not to a component.
With that DOM reference, here is the part that triggers a server-side search when the user types in a new query:
What we are doing in this is snippet is: we are taking the search input box and we are creating an Observable using
This Observable will emit a value every time that a new keyUp event occurs. To this Observable we will then apply a couple of operators:
debounceTime(150): The user can type quite quickly in the input box, and that could trigger a lot of server requests. With this operator, we are limiting the amount of server requests emitted to a maximum of one every 150ms.
distinctUntilChanged(): This operator will eliminate duplicate values
And with these two operators in place, we can now trigger a page load by passing the query string, the page size and page index to the the Data Source via the
Let's now have a look at the what the screen would look like if the user types the search term "hello":
And with this in place, we have completed our example! We now have a complete solution for how to implement an Angular Material Data Table with server-side pagination, sorting and filtering.
Let's now quickly summarize what we have learned.
The Data Table, the Data Source and related components are a good example of a reactive design that uses an Observable-based API. Let's highlight the key points of the design:
- the Material Data Table expects to receive the data from the Data Source via an Observable
- The Data Source main role is to build and provide an Observable that emits new versions of the tabular data to the Data Table
- A component class like
CourseServicewill then "glue" everything together
This reactive design helps to ensure the loose coupling of the multiple elements involved, and provides a strong separation of concerns.
Source Code + Github Running Example
A running example of the complete code is available here on this branch on Github, and it includes a small backend Express server that serves the data and does the server-side sorting/pagination/filtering.
I hope that this post helps with getting started with the Angular Material Data Table and that you enjoyed it!
If you would like to learn a lot more about Angular Material, we recommend checking the Angular Material Course, where we cover different types of widgets in much more detail.
If you have some questions or comments please let me know in the comments below and I will get back to you.
To get notified of upcoming posts on Angular Material and other Angular topics, I invite you to subscribe to our newsletter:
If you are just getting started learning Angular, have a look at the Angular for Beginners Course:
Other Angular Material posts:
Angular Material Dialog: A Complete Example