ngx-custom-modal
Version:
A custom Modal / Dialog (with inner component support) for Angular 17-20 projects with full version compatibility
585 lines (476 loc) β’ 17.2 kB
Markdown
# ngx-custom-modal
<div align="center">
[](https://www.npmjs.com/package/ngx-custom-modal)
[](https://github.com/AngelCareaga/ngx-custom-modal/actions)
[](https://github.com/AngelCareaga/ngx-custom-modal/stargazers)
[](https://angular.io/)
[](https://www.typescriptlang.org/)
[](https://opensource.org/licenses/MIT)
**A lightweight Angular modal component with signal-based reactivity and full standalone support**
[Live Examples](https://angelcareaga.github.io/ngx-custom-modal/) β’ [Documentation](https://github.com/AngelCareaga/ngx-custom-modal#readme) β’ [Report Bug](https://github.com/AngelCareaga/ngx-custom-modal/issues)
</div>
## β¨ 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
## π¨ Version 20.0.0 - Breaking Changes Notice
<div align="center">
> **β οΈ IMPORTANT: Version 20.0.0 introduces significant breaking changes**
>
> **β
Full Angular 17, 18, 19 & 20 Compatibility**
>
> Before upgrading, please review the **[CHANGELOG.md](./CHANGELOG.md)** for detailed migration instructions
</div>
### π₯ 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+`
### π 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 |
**π [Read Full Migration Guide in CHANGELOG.md](./CHANGELOG.md#breaking-changes)**
---
## π Quick Start
### Installation
```bash
npm install ngx-custom-modal
# or
yarn add ngx-custom-modal
# or
pnpm add ngx-custom-modal
```
### Basic Usage
```typescript
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
```typescript
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,
});
onModalOpened() {
console.log('Modal opened with signals!');
}
onModalClosed() {
console.log('Modal closed');
}
}
```
## π Examples
### Component Inside Modal
```typescript
@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
```typescript
@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
```typescript
@Component({
template: `
<ngx-custom-modal
#customModal
[closeOnOutsideClick]="false"
[closeOnEscape]="false"
[hideCloseButton]="true"
[size]="'lg'"
[centered]="true"
[scrollable]="true"
[animation]="true"
customClass="my-custom-modal"
>
<ng-template #modalHeader>
<h2>Custom Modal</h2>
</ng-template>
<ng-template #modalBody>
<p>This modal has custom configuration!</p>
<button (click)="customModal.close()">Manual Close</button>
</ng-template>
</ngx-custom-modal>
`,
})
export class CustomModalExample {}
```
### Using Options Object
```typescript
@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,
};
}
```
## π§ 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 |
### 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
```typescript
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;
}
```
## π¨ Styling
### Default Styles
The library comes with modern CSS custom properties for easy theming:
```css
: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:
```html
<!-- 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
```css
/* 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
```bash
git clone https://github.com/AngelCareaga/ngx-custom-modal.git
cd ngx-custom-modal
npm install
```
### Development Server
```bash
npm start
```
### Build Library
```bash
npm run build:lib
```
### Run Tests
```bash
npm test
```
### Code Formatting
This project uses [Prettier](https://prettier.io/) for code formatting:
```bash
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](LICENSE) file for details.
## π Acknowledgments
- **[angular-custom-modal](https://github.com/zurfyx/angular-custom-modal)** - Created by Gerard Rovira SΓ‘nchez, which served as inspiration for this project
- **[Stephen Paul](https://stackoverflow.com/users/1087131/stephen-paul)** - For the initial Angular 2 Modal concept
- **Angular Team** - For the amazing framework
## π Support
- π§ Email: [dev.angelcareaga@gmail.com](mailto:dev.angelcareaga@gmail.com)
- π Issues: [GitHub Issues](https://github.com/AngelCareaga/ngx-custom-modal/issues)
- π¬ Discussions: [GitHub Discussions](https://github.com/AngelCareaga/ngx-custom-modal/discussions)
- π Website: [angelcareaga.com](https://angelcareaga.com)
---
<div align="center">
**Made with β€οΈ by [Angel Careaga](https://angelcareaga.com)**
[β Star this repo](https://github.com/AngelCareaga/ngx-custom-modal/stargazers) if you found it helpful!
</div>