Marginalia

Angular: Implementing Expansion Directive with CSS Grid

Animating the height of an element between 0 and the automatically calculated size is not a straightforward task. However, it seems that a recent update to browsers has made it possible to use a CSS Grid approach.

Using this method, I implemented the directive as an easy-to-use component within an Angular application. The following sample code is fully functional, so please try it out. Feel free to incorporate it into your project if you wish.

Expandable Directive

The Expandable directive applies styles to the host element it is assigned to. As mentioned in the previous article, for the element that serves as the container for the expansion panel, you should include display: grid and grid-template-rows in the styling. This allows for animating changes in the grid structure using transition-property: grid-template-rows. You can use any values for duration and timing-function.

When applying styles using directives, you can simply pass an object to the style property through host binding. You can apply styles collectively without using features like ngStyle or [style.xxx].

@Directive({
  selector: '[expandable]',
  standalone: true,
})
export class Expandable {
  @Input({ alias: 'expandable' })
  isExpanded = false;

  @HostBinding('style')
  get styles() {
    return {
      display: 'grid',
      'transition-property': 'grid-template-rows',
      'transition-duration': '250ms',
      'transition-timing-function': 'ease',
      'grid-template-rows': this.isExpanded ? '1fr' : '0fr',
    };
  }
}

Usage

Apply the Expandable directive to any container element and add the overflow: hidden style to its immediate child elements. This will hide the overflowing content when the height of the grid becomes 0fr.

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [Expandable],
  template: `
    <h1>Expansion with grid-template-rows</h1>
    
    <button (click)="toggle()">toggle</button>
    <div [expandable]="isExpanded()" style="border: 1px solid black;">
      <div style="overflow: hidden;">
        <p>
        Lorem ipsum dolor sit amet, ...
        </p>
      </div>
    </div>
  `,
})
export class App {
  isExpanded = signal(false);

  toggle() {
    this.isExpanded.update((v) => !v);
  }
}

Thoughts

Angular has its animation feature, but I think CSS alone is sufficient for this expansion panel use case. It is a highly versatile mechanism and its implementation is not difficult, so I felt it is a technique that I want to actively use. (In the first place, it would be great if we could animate with height: auto.)

```