Angular Action Sheet


Description

This is a simple action sheet service that can be used to show action sheets in angular using material.

// action-modal.service.ts
import { CommonModule } from '@angular/common';
import { Component, Injectable, OnInit, inject } from '@angular/core';
import {
  MAT_BOTTOM_SHEET_DATA,
  MatBottomSheet,
  MatBottomSheetConfig,
  MatBottomSheetModule,
  MatBottomSheetRef,
} from '@angular/material/bottom-sheet';
import { MatButtonModule } from '@angular/material/button';
import { MatListModule } from '@angular/material/list';
import { firstValueFrom } from 'rxjs';

interface ActionI {
  name: string;
  cssClass?: string;
}

interface ActionDataI {
  actions: { [key: string]: string | ActionI };
}

interface FormattedActionI {
  name: string;
  cssClass?: string;
  key: string;
  lines?: string[];
}

@Injectable()
export class ActionModalService {
  readonly #bottomSheet = inject(MatBottomSheet);

  public async open(data: ActionDataI, options: MatBottomSheetConfig<any> = {}) {
    const dialogRef = this.#bottomSheet.open(ActionModalComponent, {
      ...options,
      data: { data },
    });
    return firstValueFrom(dialogRef.afterDismissed());
  }
}

@Component({
  standalone: true,
  imports: [CommonModule, MatButtonModule, MatBottomSheetModule, MatListModule],
  template: `<mat-nav-list>
    <button mat-list-item *ngFor="let item of actions" (click)="select(item.key)" [class]="item.cssClass || ''">
      <span matListItemTitle>{{ item.name }}</span> <span matLine *ngFor="let line of item.lines">{{ line }}</span>
    </button>
  </mat-nav-list>`,
})
export class ActionModalComponent implements OnInit {
  readonly #bottomSheetRef: MatBottomSheetRef<ActionModalComponent> = inject(MatBottomSheetRef);
  readonly #data: { data: ActionDataI } = inject(MAT_BOTTOM_SHEET_DATA);

  public actions: FormattedActionI[] = [];

  ngOnInit(): void {
    const actionList: FormattedActionI[] = this.actions;
    const actions = this.#data.data.actions;
    Object.keys(actions).forEach((key) => {
      const action = actions[key];
      let act: FormattedActionI;
      if (typeof action === 'string') act = { name: action, key };
      else act = { ...action, key };
      [act.name, ...act.lines] = act.name.split('\n');
      actionList.push(act);
    });
  }

  select(key: string) {
    this.#bottomSheetRef.dismiss({ action: true, key });
  }
}

Styling

For styling the alert modal, you have to override the following css variables. in global styles.scss file.

.mat-mdc-list-base {
  --mdc-list-list-item-label-text-color: inherit;
  --mdc-list-list-item-hover-label-text-color: inherit;
}

Setup

Import the ActionModalService and ActionModalComponent into your app module and add them to the providers and imports array respectively.

Usage

import { Component } from '@angular/core';
import { ActionModalService } from './action-modal.service';

@Component({
  selector: 'test-page',
  standalone: true,
  imports: [CommonModule],
  template: `<button (click)="remove(id)">Delete</button> `,
})
export class TestComponent implements OnInit {
  #actionModalService = inject(ActionModalService);

  async remove(id: number) {
    const result = await this.#actionModalService.open({
      actions: {
        test: 'Test\nThis is just a test',
        test2: 'Test2',
        test3: 'Test3',
        test4: {
          name: 'Test4',
          cssClass: 'red-500',
        },
      },
    });
    console.log(result);
  }
}