ngx-custom-modal
TypeScript icon, indicating that this package has built-in type declarations

20.1.0 • Public • Published

ngx-custom-modal

npm Version Build Status GitHub Stars Angular TypeScript MIT License

A lightweight Angular modal component with signal-based reactivity and full standalone support

Live ExamplesDocumentationReport Bug

✨ Features

  • 🎯 Angular Native - Built specifically for Angular 17+
  • 🎭 Standalone Components - No NgModule required
  • 🚀 Signal-Based Reactivity - Optimized performance with Angular signals
  • 🔗 Advanced Modal Stacking - Intelligent z-index management and focus trapping
  • 🎨 Bootstrap 3, 4 & 5 Compatible - Seamless integration with all Bootstrap versions
  • 📱 Mobile Friendly - Touch-optimized for mobile devices
  • Lightweight - Minimal bundle impact with performance optimization
  • 🔧 TypeScript - Full type safety and IntelliSense
  • 🎪 Custom Content - Support for components and HTML templates
  • 🎛️ Configurable - Extensive customization options
  • WCAG Compliant - Full accessibility support with screen reader compatibility
  • 🔄 Route Aware - Automatic modal closing on navigation (configurable)

🚨 Version 20.0.0 - Breaking Changes Notice

⚠️ IMPORTANT: Version 20.0.0 introduces significant breaking changes

✅ Full Angular 17, 18, 19 & 20 Compatibility

Before upgrading, please review the CHANGELOG.md for detailed migration instructions

🔥 Major Updates in v20.0.0

  • 🎨 Complete CSS/SCSS Rewrite - New custom properties system and modern styling
  • 🏗️ Enhanced HTML Structure - Improved accessibility with ARIA attributes
  • ⚡ Signal-Based Architecture - Performance optimizations with Angular signals
  • 🔧 New APIs & Services - Modal stack management and lifecycle compatibility
  • 🎯 Z-Index Management - Updated from z-index: 42 to Bootstrap-standard 1050+
  • 🔄 Route Change Detection - Automatic modal closing on navigation

📚 What You Need to Check

Component What Changed Action Required
Custom CSS Complete styling overhaul Review and update custom styles
Event Handling New granular events (opening, opened, closing, closed) Update event listeners
Testing New signal-based testing patterns Update test assertions
Accessibility Enhanced ARIA attributes Verify accessibility implementations
Route Behavior Automatic modal closing on navigation (enabled by default) Configure if different behavior needed

👉 Read Full Migration Guide in CHANGELOG.md


🚀 Quick Start

Installation

npm install ngx-custom-modal
# or
yarn add ngx-custom-modal
# or
pnpm add ngx-custom-modal

Basic Usage

import { NgxCustomModalComponent } from 'ngx-custom-modal';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [NgxCustomModalComponent],
  template: `
    <button (click)="modal.open()">Open Modal</button>

    <ngx-custom-modal #modal>
      <ng-template #modalHeader>
        <h2>Modal Title</h2>
      </ng-template>
      <ng-template #modalBody>
        <p>Modal content goes here!</p>
      </ng-template>
    </ngx-custom-modal>
  `,
})
export class ExampleComponent {}

Usage with Angular 17 Features

import { Component, signal } from '@angular/core';
import { NgxCustomModalComponent, ModalOptions } from 'ngx-custom-modal';

@Component({
  selector: 'app-signal-example',
  standalone: true,
  imports: [NgxCustomModalComponent],
  template: `
    <button (click)="modal.open()">Open Signal Modal</button>

    <ngx-custom-modal
      #modal
      [size]="modalSize()"
      [options]="modalOptions()"
      (opened)="onModalOpened()"
      (closed)="onModalClosed()"
    >
      <ng-template #modalHeader>
        <h2>{{ modalTitle() }}</h2>
      </ng-template>

      <ng-template #modalBody>
        @if (showContent()) {
          <p>{{ modalContent() }}</p>
          @for (item of items(); track item.id) {
            <div class="item">{{ item.name }}</div>
          }
        }
      </ng-template>
    </ngx-custom-modal>
  `,
})
export class SignalExampleComponent {
  modalTitle = signal('Signal-Based Modal');
  modalContent = signal('This modal uses Angular 17 features!');
  modalSize = signal<'sm' | 'md' | 'lg' | 'xl'>('lg');
  showContent = signal(true);

  items = signal([
    { id: 1, name: 'Signal-based reactivity' },
    { id: 2, name: 'Control flow syntax' },
    { id: 3, name: 'Performance optimization' },
  ]);

  modalOptions = signal<ModalOptions>({
    closeOnOutsideClick: true,
    closeOnEscape: true,
    animation: true,
    centered: true,
    closeOnRouteChange: true, // Default: true - automatically close on navigation
  });

  onModalOpened() {
    console.log('Modal opened with signals!');
  }

  onModalClosed() {
    console.log('Modal closed');
  }
}

📋 Examples

Component Inside Modal

@Component({
  template: `
    <button (click)="componentModal.open()">Open Component Modal</button>

    <ngx-custom-modal #componentModal [size]="'lg'">
      <ng-template #modalHeader>
        <h2>Component Modal</h2>
      </ng-template>
      <ng-template #modalBody>
        <app-my-component [data]="componentData"></app-my-component>
      </ng-template>
      <ng-template #modalFooter>
        <button (click)="componentModal.close()" class="btn btn-secondary">Close</button>
      </ng-template>
    </ngx-custom-modal>
  `,
})
export class ComponentModalExample {
  componentData = { message: 'Hello from component!' };
}

Nested Modals

@Component({
  template: `
    <ngx-custom-modal #parentModal [size]="'xl'">
      <ng-template #modalHeader>
        <h2>Parent Modal</h2>
      </ng-template>
      <ng-template #modalBody>
        <p>This is the parent modal with automatic stack management.</p>
        <button (click)="childModal.open()">Open Child Modal</button>

        <ngx-custom-modal #childModal [size]="'md'" [centered]="true">
          <ng-template #modalHeader>
            <h3>Child Modal</h3>
          </ng-template>
          <ng-template #modalBody>
            <p>This is a nested modal with proper z-index handling!</p>
          </ng-template>
        </ngx-custom-modal>
      </ng-template>
    </ngx-custom-modal>
  `,
})
export class NestedModalExample {}

Custom Configuration

@Component({
  template: `
    <ngx-custom-modal
      #customModal
      [closeOnOutsideClick]="false"
      [closeOnEscape]="false"
      [hideCloseButton]="true"
      [size]="'lg'"
      [centered]="true"
      [scrollable]="true"
      [animation]="true"
      [closeOnRouteChange]="false"
      customClass="my-custom-modal"
    >
      <ng-template #modalHeader>
        <h2>Custom Modal</h2>
      </ng-template>
      <ng-template #modalBody>
        <p>This modal has custom configuration!</p>
        <p>It will NOT close automatically when navigating to another route.</p>
        <button (click)="customModal.close()">Manual Close</button>
      </ng-template>
    </ngx-custom-modal>
  `,
})
export class CustomModalExample {}

Using Options Object

@Component({
  template: `
    <ngx-custom-modal #optionsModal [options]="modalOptions">
      <ng-template #modalHeader>
        <h2>Options Modal</h2>
      </ng-template>
      <ng-template #modalBody>
        <p>Configured via options object</p>
      </ng-template>
    </ngx-custom-modal>
  `,
})
export class OptionsModalExample {
  modalOptions: ModalOptions = {
    closeOnOutsideClick: false,
    closeOnEscape: true,
    customClass: 'my-modal-class',
    hideCloseButton: false,
    size: 'lg',
    centered: true,
    scrollable: false,
    animation: true,
    animationDuration: 300,
    backdrop: 'dynamic',
    keyboard: true,
    focus: true,
    closeOnRouteChange: true,
  };
}

Route-Aware Modal Configuration

@Component({
  template: `
    <!-- Modal that stays open during navigation -->
    <ngx-custom-modal #persistentModal [closeOnRouteChange]="false">
      <ng-template #modalHeader>
        <h2>Persistent Modal</h2>
      </ng-template>
      <ng-template #modalBody>
        <p>This modal will remain open even when you navigate to other pages.</p>
        <p>Useful for shopping carts, music players, or global notifications.</p>
      </ng-template>
    </ngx-custom-modal>

    <!-- Modal that closes on navigation (default behavior) -->
    <ngx-custom-modal #standardModal>
      <ng-template #modalHeader>
        <h2>Standard Modal</h2>
      </ng-template>
      <ng-template #modalBody>
        <p>This modal will automatically close when navigating to other pages.</p>
        <p>This is the default behavior for better UX.</p>
      </ng-template>
    </ngx-custom-modal>
  `,
})
export class RouteAwareModalExample {}

🔧 API Reference

Component Properties

Property Type Default Description
closeOnOutsideClick boolean true Close modal when clicking outside
closeOnEscape boolean true Close modal when pressing Escape key
customClass string '' Custom CSS class for the modal
hideCloseButton boolean false Hide the default close button
options ModalOptions {} Configuration options object
size 'sm' | 'md' | 'lg' | 'xl' 'md' Modal size
centered boolean false Center modal vertically
scrollable boolean false Make modal body scrollable
animation boolean true Enable/disable animations
backdrop 'static' | 'dynamic' 'dynamic' Backdrop behavior
keyboard boolean true Enable keyboard interactions
focus boolean true Enable focus management
closeOnRouteChange boolean true Close modal on route navigation

Events

Event Type Description
opening EventEmitter<void> Emitted when modal starts opening
opened EventEmitter<void> Emitted when modal is fully opened
closing EventEmitter<void> Emitted when modal starts closing
closed EventEmitter<void> Emitted when modal is fully closed

Template References

Template Ref Type Description
#modalHeader TemplateRef Header content template
#modalBody TemplateRef Body content template
#modalFooter TemplateRef Footer content template

Methods

Method Returns Description
open() void Opens the modal
close() void Closes the modal
toggle() void Toggles modal visibility
isTopMost() boolean Checks if modal is topmost

ModalOptions Interface

interface ModalOptions {
  closeOnOutsideClick?: boolean;
  closeOnEscape?: boolean;
  customClass?: string;
  hideCloseButton?: boolean;
  backdrop?: 'static' | 'dynamic';
  keyboard?: boolean;
  focus?: boolean;
  size?: 'sm' | 'md' | 'lg' | 'xl';
  centered?: boolean;
  scrollable?: boolean;
  animation?: boolean;
  animationDuration?: number;
  closeOnRouteChange?: boolean;
}

🔄 Route Change Behavior

Default Behavior (Recommended)

By default, modals will automatically close when the user navigates to a different route. This provides a better user experience and prevents modals from appearing in unexpected contexts.

// Default behavior - modal closes on navigation
<ngx-custom-modal #modal>
  <!-- Modal content -->
</ngx-custom-modal>

// Explicitly enabled
<ngx-custom-modal #modal [closeOnRouteChange]="true">
  <!-- Modal content -->
</ngx-custom-modal>

Persistent Modals

For specific use cases where you want the modal to persist across route changes (shopping carts, media players, global notifications), you can disable this behavior:

// Modal persists across route changes
<ngx-custom-modal #modal [closeOnRouteChange]="false">
  <!-- Modal content -->
</ngx-custom-modal>

// Via options object
modalOptions: ModalOptions = {
  closeOnRouteChange: false,
  // other options...
};

Migration from Previous Versions

If you're upgrading from a previous version and have modals that were designed to persist across routes, you'll need to explicitly set closeOnRouteChange: false:

// Before v20.0.0 (modals persisted by default)
<ngx-custom-modal #modal>
  <!-- Modal content -->
</ngx-custom-modal>

// After v20.0.0 (to maintain same behavior)
<ngx-custom-modal #modal [closeOnRouteChange]="false">
  <!-- Modal content -->
</ngx-custom-modal>

🎨 Styling

Default Styles

The library comes with modern CSS custom properties for easy theming:

:root {
  /* Modal backdrop */
  --modal-backdrop-bg: rgba(0, 0, 0, 0.5);
  --modal-backdrop-blur: 2px;

  /* Modal content */
  --modal-content-bg: #fff;
  --modal-content-border: 1px solid rgba(0, 0, 0, 0.125);
  --modal-content-border-radius: 0.5rem;
  --modal-content-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);

  /* Animations */
  --modal-animation-duration: 200ms;
  --modal-z-index: 1050;
}

/* Basic modal styles */
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  min-height: 100%;
  background-color: var(--modal-backdrop-bg);
  z-index: var(--modal-z-index);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity var(--modal-animation-duration) ease-in-out;
  backdrop-filter: blur(var(--modal-backdrop-blur));
}

.modal.in {
  opacity: 1;
}

.modal-content {
  background-color: var(--modal-content-bg);
  border: var(--modal-content-border);
  border-radius: var(--modal-content-border-radius);
  box-shadow: var(--modal-content-shadow);
  max-width: 500px;
  width: 90%;
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  border-bottom: 1px solid #dee2e6;
}

.modal-body {
  padding: 1rem;
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  padding: 0.75rem;
  border-top: 1px solid #dee2e6;
}

.close {
  background: none;
  border: none;
  font-size: 1.5rem;
  cursor: pointer;
  padding: 0.25rem;
  opacity: 0.5;
  transition: opacity 0.15s ease-in-out;
}

.close:hover {
  opacity: 0.75;
}

Bootstrap Integration

For Bootstrap users, ngx-custom-modal works seamlessly with all Bootstrap versions:

<!-- Bootstrap Modal Example -->
<ngx-custom-modal #bootstrapModal [size]="'lg'" [centered]="true">
  <ng-template #modalHeader>
    <h1 class="modal-title fs-5">Bootstrap Modal</h1>
  </ng-template>
  <ng-template #modalBody>
    <div class="container-fluid">
      <div class="row">
        <div class="col-md-6">
          <p class="text-muted">Left column content</p>
        </div>
        <div class="col-md-6">
          <p class="text-muted">Right column content</p>
        </div>
      </div>
    </div>
  </ng-template>
  <ng-template #modalFooter>
    <button type="button" class="btn btn-secondary" (click)="bootstrapModal.close()">Close</button>
    <button type="button" class="btn btn-primary">Save changes</button>
  </ng-template>
</ngx-custom-modal>

Dark Mode Support

/* Dark mode support */
@media (prefers-color-scheme: dark) {
  :root {
    --modal-backdrop-bg: rgba(0, 0, 0, 0.8);
    --modal-content-bg: #1f2937;
    --modal-content-border: 1px solid #374151;
    --modal-text-color: #f9fafb;
  }
}

/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
  :root {
    --modal-animation-duration: 0ms;
  }
}

/* High contrast mode */
@media (prefers-contrast: high) {
  :root {
    --modal-content-border: 2px solid currentColor;
    --modal-backdrop-bg: rgba(0, 0, 0, 0.9);
  }
}

🌍 Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)
  • Mobile browsers (iOS Safari, Chrome Mobile)

🛠️ Development

Prerequisites

  • Node.js 18+
  • Angular CLI 17+

Setup

git clone https://github.com/AngelCareaga/ngx-custom-modal.git
cd ngx-custom-modal
npm install

Development Server

npm start

Build Library

npm run build:lib

Run Tests

npm test

Code Formatting

This project uses Prettier for code formatting:

npm run format

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Development Guidelines

  • Follow the existing code style
  • Add tests for new features
  • Update documentation for any API changes
  • Use conventional commit messages

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • angular-custom-modal - Created by Gerard Rovira Sánchez, which served as inspiration for this project
  • Stephen Paul - For the initial Angular 2 Modal concept
  • Angular Team - For the amazing framework

📞 Support


Made with ❤️ by Angel Careaga

⭐ Star this repo if you found it helpful!

Package Sidebar

Install

npm i ngx-custom-modal

Weekly Downloads

344

Version

20.1.0

License

MIT

Unpacked Size

128 kB

Total Files

5

Last publish

Collaborators

  • angelcareaga