This tutorial is out of date and no longer maintained.
Let’s look at a very common use case. You have a list of items, you need to display all nicely on the screen in card form.
It looks okay but you want all cards to always maintain the same height. It should always match the height of the tallest object, and resize accordingly when screen size changed, like this:
We can achieve this by using creating a custom directive.
Interesting? Let’s code.
GitHub: https://github.com/chybie/ng-musing/tree/master/src/app/same-height
Let’s look at our main component.
import { Component } from '@angular/core';
@Component({
selector: 'page-same-height',
template: `
<main class="container">
<h2>Malaysia States</h2>
<div class="row">
<div class="col-sm-4" *ngFor="let state of list">
<div class="card card-block">
<h4 class="card-title">{{ state.title }}</h4>
<p class="card-text">
{{ state.content }}
</p>
</div>
</div>
</div>
</main>
`
})
export class PageSameHeightComponent {
list = [
{
title: 'Selangor',
content: 'Selangor is a state ....'
},
{
title: 'Kuala Lumpur',
content: 'Kuala Lumpur is the capital of Malaysia...'
},
{
title: 'Perak',
content: 'Perak is a state in the northwest of Peninsular Malaysia...'
}
]
}
The code is pretty expressive itself. We have a list
of states. We loop the list with *ngFor
in our template and display each item accordingly.
Please note that in this example, I am using Boostrap 4 to style the CSS, but it’s not necessary.
There are a few ways to match the height. In this tutorial, we will match height by using the CSS class name.
In our example, we want to match the height of all elements with class name card on the same row. Row
is the parent and all Card
are the children.
To align all the children Card
of the Row
, let’s modify our HTML template and assign an attribute called myMatchHeight
to the row and pass in card
as the attribute value.
...
@Component({
selector: 'page-same-height',
template: `
<main class="container">
<h2>Malaysia States</h2>
<!-- Assign myMatchHeight here -->
<div class="row" myMatchHeight="card">
<div class="col-sm-4" *ngFor="let state of list">
<div class="card card-block">
...
Now you might be wondering, where is the myMatchHeight
coming from? That is the custom directive that we are going to build next!
Let’s create our match height directive.
import {
Directive, ElementRef, AfterViewChecked,
Input, HostListener
} from '@angular/core';
@Directive({
selector: '[myMatchHeight]'
})
export class MatchHeightDirective implements AfterViewChecked {
// class name to match height
@Input()
myMatchHeight: string;
constructor(private el: ElementRef) {
}
ngAfterViewChecked() {
// call our matchHeight function here later
}
matchHeight(parent: HTMLElement, className: string) {
// match height logic here
}
}
@Directive
decorator. We specify [myMatchHeight]
in the selector, this means that we use it as an attribute in any HTML tag. e.g. We use that in our main component.myMatchHeight
with the same name as our selector. By doing this, we can then use it like this myMatchHeight="some_value"
, some_value
will then be assigned to myMatchHeight
variable. e.g. We use that in our main component, we pass in card
as the value.AfterViewChecked
.matchHeight
function.Let’s breakdown what should we do step by step:-
Let’s code it.
...
matchHeight(parent: HTMLElement, className: string) {
// match height logic here
if (!parent) return;
// step 1: find all the child elements with the selected class name
const children = parent.getElementsByClassName(className);
if (!children) return;
// step 2a: get all the child elements heights
const itemHeights = Array.from(children)
.map(x => x.getBoundingClientRect().height);
// step 2b: find out the tallest
const maxHeight = itemHeights.reduce((prev, curr) => {
return curr > prev ? curr : prev;
}, 0);
// step 3: update all the child elements to the tallest height
Array.from(children)
.forEach((x: HTMLElement) => x.style.height = `${maxHeight}px`);
}
...
I guess the code is very expressive itself.
Now that we have completed our match height logic, let’s use it.
...
ngAfterViewChecked() {
// call our matchHeight function here
this.matchHeight(this.el.nativeElement, this.myMatchHeight);
}
...
Remember to import the directive in your app module
and add it in declarations
.
Refresh your browser and you should see all the cards are with same height!
Now, let’s resize your browser. The card height is not adjusted automatically until you refresh the browser again. Let’s update our code.
We need to listen to the window resize event and update the elements’ height.
...
@HostListener('window:resize')
onResize() {
// call our matchHeight function here
this.matchHeight(this.el.nativeElement, this.myMatchHeight);
}
...
Another problem you’ll see is that when you scale down your browser, the height will be updated accordingly (grow taller). However, when you scale up your browser, the height is not updated (not shrink down).
Why is this happening? It is because once the card size grows taller, all content can fit in and no height adjustment is needed.
To solve this, we need to reset the height
of all elements before we recalculate the tallest height. We do this after step 1.
...
matchHeight(parent: HTMLElement, className: string) {
// step 1: find all the child elements with the selected class name
const children = parent.getElementsByClassName(className);
if (!children) return;
// step 1b: reset all children height
Array.from(children).forEach((x: HTMLElement) => {
x.style.height = 'initial';
});
...
}
...
That’s it. Remember that whenever we need to manipulate DOM element, it’s recommended that we do it in the directive. Creating a custom directive and listening to the custom event is easy with Angular Directive.
Our directive works well with any element including nested components too. Check out the source code for another 2 more examples.
GitHub: https://github.com/chybie/ng-musing/tree/master/src/app/same-height
Happy coding!
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!