Skip to content

Repository files navigation

modal-lib2

A lightweight, standalone, SSR-safe modal system for Angular 17+.

modal-lib2 provides a simple service-based API for rendering any standalone Angular component as a modal.

Designed to be:

  • headless
  • predictable
  • Signals-friendly
  • Angular-aligned
  • SSR-safe

Why does this exist?

modal-lib2 was created for developers who want complete control over modal content and styling without adopting a heavily opinionated UI framework or dialog system.

The philosophy is simple:

  • you own the modal UI
  • you own the styling
  • you own the component
  • the library handles the modal lifecycle safely and predictably

modal-lib2 intentionally stays small and headless rather than trying to become a complete design system.


Live Demo


Features

  • Angular 17+ standalone component support
  • Angular 21 compatible
  • Simple ModalService.open() API
  • Pass data into modal components via @Input()
  • Signal-friendly data assignment support
  • Strongly typed modal results via ModalRef<TResult>
  • Optional backdrop support
  • Optional ESC-to-close behavior
  • Optional body scroll locking
  • Programmatic close support
  • Idempotent cleanup (close() is safe to call multiple times)
  • SSR-safe design
  • Avoids DOM access during server rendering
  • Headless by design (bring your own styles/UI)

Installation

npm install modal-lib2

Basic Usage

Modal Content Component

import { Component, inject, Input } from '@angular/core';
import { ModalRef } from 'modal-lib2';

export interface LoginResult {
  success: boolean;
  token?: string;
}

@Component({
  selector: 'app-login-modal',
  standalone: true,
  template: `
    <h3>Login</h3>

    <p>Hello, {{ username }}</p>

    <button (click)="ok()">OK</button>
    <button (click)="cancel()">Cancel</button>
  `,
})
export class LoginModalComponent {

  @Input() username = '';

  private modalRef =
    inject<ModalRef<LoginResult>>(ModalRef);

  ok() {
    this.modalRef.close({
      success: true,
      token: '123'
    });
  }

  cancel() {
    this.modalRef.close({
      success: false
    });
  }
}

Open Modal

import { Component, inject } from '@angular/core';
import { ModalService } from 'modal-lib2';

import {
  LoginModalComponent,
  LoginResult
} from './login-modal.component';

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    <button (click)="open()">
      Open Login
    </button>
  `,
})
export class AppComponent {

  private modal = inject(ModalService);

  open() {

    const { ref, instance } =
      this.modal.open<LoginModalComponent, LoginResult>(
        LoginModalComponent,
        {
          username: 'Burt'
        },
        {
          backdrop: true,
          closeOnEsc: true,
          lockScroll: true,
        }
      );

    ref.afterClosed.subscribe(result => {
      console.log('Modal closed with:', result);
    });

    console.log('Modal instance:', instance);
  }
}

API

ModalService.open<T, TResult>()

open<T, TResult>(
  component: Type<T>,
  data?: Partial<T>,
  options?: ModalOptions
)

Parameters

component

Standalone Angular component to render inside the modal.


data

Partial object assigned to the component instance.

Supports standard assignment:

{
  username: 'burt'
}

Supports signal-friendly nested structures:

{
  data: {
    stuff: {
      moreStuff: {
        hi: 'there'
      }
    }
  }
}

options

export type ModalOptions = {
  backdrop?: boolean;
  closeOnEsc?: boolean;
  lockScroll?: boolean;
};
Option Default
backdrop true
closeOnEsc true
lockScroll false

Return Value

{
  ref: ModalRef<TResult>;
  instance: T | undefined;
  close: () => void;
}

Notes

  • instance is undefined during SSR/server rendering
  • close() is always safe to call

ModalRef

class ModalRef<TResult> {

  afterClosed:
    Observable<TResult | undefined>;

  close(result?: TResult): void;
}

Notes

  • afterClosed emits once and completes
  • close() is idempotent
  • Calling close() multiple times has no additional effect

SSR Notes

modal-lib2 avoids direct DOM access during server rendering and is designed to work safely in Angular SSR environments.

Modal rendering occurs only in browser environments.


Design Notes

  • Modal options are non-sticky
  • Each open() call is evaluated independently
  • Scroll locking is opt-in
  • ESC closing is configurable per modal
  • Layout/styling is intentionally left to consumers

The library owns:

  • overlay lifecycle
  • centering
  • stacking
  • viewport behavior
  • cleanup
  • SSR safety

Consumers own:

  • modal appearance
  • animations
  • themes
  • internal layout/UI

Changelog Highlights

1.1.0

  • Angular 21 compatibility
  • Improved modal container positioning/layering
  • Updated packaging/type definitions
  • Public StackBlitz demo added

1.0.0

  • Service-based modal API
  • Strongly typed modal results
  • Signal-friendly data handling
  • ESC, backdrop, and scroll-lock options
  • Idempotent cleanup behavior
  • SSR-safe design

About

A clean, customizable modal component for Angular apps. Easy to plug in.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages