How I implemented sticky columns in tables using directives - Angular!

Full stack web developer with main focus in Angular and NodeJs. Loves creating blogs, a side projects when I can find time.
Search for a command to run...

Full stack web developer with main focus in Angular and NodeJs. Loves creating blogs, a side projects when I can find time.
No comments yet. Be the first to comment.
People who are familiar with Figma would have noticed that the input fields support dragging to increase or decrease values. Instead of having to click on the input field first and then type the number in, the dragging feature is really handy as you ...

Show customizable markers on the calendar easily.

In the previous article, I shared how to set up Coolify and deploy a Node.js application on a VPS using Nixpacks. It is the easiest way to deploy applications using Coolify. We don't need any knowledge of Docker. Read Here: Deploy Node.js application...

Beginners guide on how to easily deploy a node application on a VPS.

Let's look at one way to architect Angular's highly customizable and dynamic card list component. The goal is to make it easier to render different kinds of card lists using one single component. Dynamic Card List Component The component should be dy...

How to create sticky columns in Angular using directives. Implementing tables with sticky columns can be tricky especially when you have to make multiple columns sticky. Using directives, we can easily implement sticky columns.
I can't emphasize more the power of directives in Angular. I've written a couple of articles showcasing how one can actually use it to implement really cool stuff. You can check some use-cases for directives here: Angular Directive Showcase.

We make use of the position: sticky CSS property to make an element sticky. Read more about sticky positioning at MDN.
.sticky {
position: sticky;
left: 0;
}
For the sticky position to work properly, at least one of top, right, bottom, or left should be specified.
Making the first column in a table sticky is super simple, you basically add the sticky class to the column.
When two columns need to stick to the left, we can't just add the sticky class to both of the columns. This is how it looks if you did so:

Here you can see the Manager column overlapping with the Company column. This is because we gave both the columns left:0.
To make it work as expected, the styles should be like this:
.company {
position: sticky;
left: 0px;
}
.manager {
position: sticky;
left: 120px; // <-- width of the company column
}
What we did here is we added the offset of the Manager column as the left property value.
For calculating the left value, we need to find the x value of the column. If we look at the first column Company and get its offset from the left side.

We expect the x value to be 0 but we get 85 here. This is because the x value is calculated from the left side of the window to the column. For getting the left threshold of the column with respect to the table, we need to find the x value of the table. Once we get the table's offset, we can subtract it from the offset of the column.

Example of the calculation:
We are going to create a directive to do just that. The directive can then be placed on the columns which need to be sticky. If you are thinking about why a directive for this particular use case, the calculation of the sticky thresholds can be done easily. Creating a directive makes it easy to re-use the functionality for different elements.
import { CommonModule } from '@angular/common';
import {
AfterViewInit,
Directive,
ElementRef,
NgModule,
Optional,
} from '@angular/core';
@Directive({
selector: '[stickyTable]',
})
export class StickyTableDirective {
constructor(private el: ElementRef) {}
get x() {
return (this.el.nativeElement as HTMLElement)?.getBoundingClientRect()?.x;
}
}
@Directive({
selector: '[sticky]',
})
export class StickyDirective implements AfterViewInit {
constructor(
private el: ElementRef,
@Optional() private table: StickyTableDirective
) {}
ngAfterViewInit() {
const el = this.el.nativeElement as HTMLElement;
const { x } = el.getBoundingClientRect();
el.style.position = 'sticky';
el.style.left = this.table ? `${x - this.table.x}px` : '0px';
}
}
@NgModule({
declarations: [StickyDirective, StickyTableDirective],
imports: [CommonModule],
exports: [StickyDirective, StickyTableDirective],
})
export class StickyDirectiveModule {}
If you look at the code above, we have two directives:
The second directive is really interesting here. Why do we need a second directive?
We have a separate directive that can be placed on the table to get the offset of the table. The directive can then be injected inside the main StickyDirective.
constructor(
private el: ElementRef,
@Optional() private table: StickyTableDirective
) {}
We mark the StickyTableDirective as @Optional() so that we can add the StickyDirective directive on other elements and it can automatically be sticky with the default value.
Ref: https://angular.io/guide/hierarchical-dependency-injection#optional
Here is how we use it in HTML.
<table stickyTable>
<tr>
<th sticky>Company</th>
<th sticky>Manager</th>
<th>Employees</th>
<th>Contractors</th>
<th>Jobs</th>
<th>Contracts</th>
<th>Vacancy</th>
<th>Offices</th>
</tr>
<ng-container *ngFor="let item of data">
<tr>
<td sticky style="min-width:200px">{{ item.company }}</td>
<td sticky>{{ item?.manager }}</td>
<td> {{ item?.employees }} </td>
<td> {{ item?.contractors }} </td>
<td> {{ item?.employees }} </td>
<td> {{ item?.contractors }} </td>
<td> {{ item?.employees }} </td>
<td> {{ item?.contractors }} </td>
</tr>
</ng-container>
</table>
We add the stickyTable directive to the table and the sticky directive to the column.
A lot of improvements can be made to this directive to make it more re-usable like:
StickyTableDirective to be able to use it on other elements as well.For the sake of keeping the example here simple, I've kept it simple.
Do add your thoughts in the comments section. Stay Safe ❤️