Angular CDK Tables

Zack DeRose
Angular In Depth
8 min readJul 19, 2018

--

AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!

I tag-teamed with Angular Firebase for this article! Here’s a kick-ass video of lots of the stuff this article will cover:

In this article:

  • Angular CDK Tables
  • Bootstrap 4 with Angular CDK Tables
  • Client Side searching/paging/sorting

Simple Table

(https://github.com/ZackDeRose/simple-table)

Let’s get started by creating a simple table to show some heroes.

ng new simple-table
npm install --save @angular/cdk
ng g component table

Add the CdkTableModule to your app.module.ts file:

Inside of the table.component.ts, we’ll start by creating a BehaviorSubject of an array of heroes, as well as a BehaviorSubject of the columns to show in our table:

This tableDataSource$will serve as our DataSource input for our Angular CDK Table. And we’ll use displayedColumns$ to keep track of the columns that our CDK Table should show.

[Note: I’m using a BehaviorSubject of an array for displayedCoulmns$ here instead of just a regular array so that if we decided to later add functionality to hide/show columns we could react to the fact that they changed via a subscription to displayedColumns$. We won’t be doing anything that requires that in this demo, so a normal array is absolutely valid here. Note also though, that you can also synchronously get the current value of a BehaviorSubject with the syntax myBehaviorSubject$.value, so in a sense, it’s the best of both worlds, and why the author is particularly found of BehaviorSubjects!]

Next we’ll create our template using the table CDK guide for a starting point.

The basic steps for developing CDK tables are to first define a template for how your table’s headers, footers, and regular cells look like for each column of your table. To do this, we’ll create a<ng-container> element for each of our columns that we would want to show. Here’s an example of an ng-container for the name of our heroes:

The cdkHeaderCellDef structural directive lets the CDK Table know to to use the <th> tag in the template above for a header, while the cdkCellDef directive specifies the <td> tag as the template for each table cell in this Name column. The cdkColumnDef also exports the row content so we can reference it here in our template.

After defining each column, we then provide the following tags to give the CDK Table instructions on which columns to display for each row:

This tells the CDK Table which columns to actually show. For example — we could splice out one of the items in our displayedColumns$ array here to exclude one of our columns from being rendered (without having to touch the corresponding <ng-container> for that column).

Note also that we can still add input/outputs to these <tr> tags, like a (click) on an entire row, or [draggable] / (dragstart) / (dragenter) / (dragend) to support dragging and dropping in your table. Also note that *cdkRowDef also provides access to the row context, so we could include the data for that row inside those ins/outs we set on the <tr> here!

All this put together now gets us a working table:

But it’s not a very exciting table yet. Let’s add another column to this row to let our user level up their heroes.

To do this well add another column to our displayedColumns$ array and add another <ng-container> to our template.

Let’s also create a Dictionary object to hold our heroes to make it easier to figure out which one to level up.

Since we’re managing our heroes separately from our table’s data source, we’ll set up our table data source to react to changes to our heroes data inside of the OnInit lifecycle hook:

Now whenever we change our heroes, our tableDataSource will be updated to reflect those changes.

Finally let’s write our levelUp() function to call whenever a user clicks level up:

Now we’ve got a table that does some neat stuff:

But it’s still kinda lame to look at.

Using Bootstrap with Angular CDK Tables

(https://github.com/ZackDeRose/simple-bootstrap-table)

It’s actually very easy to apply the @angular/material package on top of a CDK Table, since @angular/material is built on top of @angular/cdk. All you have to do is go through their Getting Started instructions, and then Find/Replace cdk with mat in our template from the previous section.

Assuming though that we want to use a method of controlling our styling with a non-Material CSS framework like Bootstrap 4, we can still very easily/intuitively apply styles to our table.

To start, we’ll reference the bootstrap CDN inside of our app’s index.html page. (If you prefer to package bootstrap CSS with your project, you can also npm install — save bootstrap-css-only [npm] and then add a reference to this package inside of your angular.json in the styles array.)

Now that we have the css we can simply add the table table-hover classes to our <table> tag in our table.component.html template, and we’ll get a vanilla bootstrap table. Let’s also add btn btn-primary classes to our button to complete the effect:

For fun, let’s add some dynamic styling with [ngClass]. For every stat, we’ll mark the Hero with the highest and lowest amount for that stat in our table by marking the cells with the table-success class for the highest, and table-danger for the lowest.

To do this, let’s setup another BehaviorSubject of a dictionary structure so we can always look up the hero with the highest and lowest of a given stat in constant time.

And now we’ll add this to the the <ng-container> for each column of our table:

Let’s go ahead and add some more heroes to this table and change around their stats and level up mechanics to make things a bit more interesting.

And voila! Got some decent table-tude going on naow.

Client-side paging/searching/sorting

(repo: https://github.com/ZackDeRose/searching-sorting-paged-table)

Now that we’ve got a decent core table put together, let’s add some more table-y features.

Pagination

First let’s add pagination. To start, we’ll import ng-bootstrap to our project, so we can use their provided pagination component.

npm install — save @ng-bootstrap/ng-bootstrap

Add NgbModule to app.module.ts’s imports:

This component will handle most of the pagination work for us, but there are a few changes we’ll need to make to our rxjs pipes that we set up earlier.

Let’s start by making a currentPage$ and pageSize$ BehaviorSubject in our table component, as well as another BehaviorSubject to track which of our dataSources we actually want on the current page:

Inside of our ngOnInit() now, we’ll want to set up our dataOnPage$ to listen to any changes to currentPage$, pageSize$, or tableDataSources$ so it always contains the correct items on the page.

To do this, we’ll use the static rxjs operator combineLatest() to listen to changes to any of these:

Finally we’ll add pagination to our template, and change our data source for the table to our new dataOnPage$ Observable:

And boom!! We have client-side pagination!

Searching

Let’s add a search bar to filter out our results as well. We’ll just use a standard reactive form to accomplish this. We’ll need to import the FormsModule and the ReactiveFormsModule from @angular/forms:

And now we’ll add a searchFormControl to our component code:

searchFormControl = new FormControl();

And inside of ngOnInit(), we’ll remove the code to create our tableDataSource$ from the heroes$ subscription and move it into a subscription to combineLatest() of heroes$ and searchFormControl.valueChanges. We’ll filter out heroes that don’t match our search so they don’t make it into the tableDataSource$:

We call searchFormControl.setValue('') to make sure that the form control valueChanges observable fires when the component loads (combineLatest() won’t start emitting until each of the observables passed in have emitted something. Since heroes$ is a BehaviorSubject, it emits a value as soon as it’s initialized, but searchFormControl.valueChanges won’t emit until the value actually changes).

And now bit of rearranging of our template to add a search bar above the table:

And now we’ve got search!

Sort

As a final touch, let’s add in sorting. We’ll start with another BehaviorSubject to record what column to sort on, and the direction. We’ll start with ‘name’ and ‘asc’:

sortKey$ = new BehaviorSubject<string>('name');sortDirection$ = new BehaviorSubject<string>('asc');

And now we’ll adjust our combineLatest() of heroes$ and searchFormControl.valueChanges to add in sorting instructions. After filtering out things that don’t match our search, we’ll sort our array based on the sorting column and direction held in our BehaviorSubjects.

Now that our sorting logic is set up, we’ll adjust our template to give our table headers a click event that will adjust our sorting BehaviorSubjects. We’ll also add an indicator so our user will know how the table is sorted:

And here’s our adjustSort() function:

And that’s it!

Let me know how I did!

Hoping that this article is beneficial to people out there interested in Angular CDK Tables, and provides some fundamentals and sparks some ideas on how y’all could use this excellent tool in your apps/projects!

This was my first authored article, so let me know how I can improve!

I’d love to continue this topic and explore server-side paging/sorting/searching — specifically using a Google Firebase Firestore backend, paired with the @ngrx/entity package. There’s some really cool and powerful stuff going on there in my opinion! Let me know if there’d be interest in that topic.

Acknowledgements

Huge thanks to @Jeffdelaney23 for the idea to create this article and for making a video of this article!! I’ve been a Pro member of Angular Firebase for 1 quarter now and have absolutely loved the experience. Will definitely be renewing my membership for a long time to come.

Also big thanks to Swampfox, my employer, for encouraging me to pursue this. Y’all continue to exceed all expectations of what a great team can achieve, and I honestly love getting up every morning knowing that together, we’re going to crush it.

--

--

Zack DeRose
Angular In Depth

Father, Senior Engineer at @nrwl_io. Game development hobbyist.