Modal Using Portal
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
Creating Modals Using UX Portal System
- Index: 3 -
Overview
This guide demonstrates how to create functional modals using the UX Portal system instead of traditional modal services. The UX Portal approach provides better performance, more flexibility, and easier integration with Angular applications.
🎯 Why Use UX Portal for Modals?
Advantages Over Traditional Modal Services
- ✅ No Service Setup Required: No need to call
setModalComponent()or configure modal services - ✅ Better Performance: Optimized rendering with proper cleanup and memory management
- ✅ More Flexible: Advanced positioning, animations, and viewport constraints
- ✅ Theme Integration: Seamless integration with your theme system
- ✅ Production Ready: Battle-tested in real applications
- ✅ String Content Support: Easy to create complex forms with embedded HTML/JavaScript
Common Modal Service Issues
- ❌ Setup Required: Traditional modal services require
setModalComponent()calls - ❌ Component Dependencies: Need separate modal component files
- ❌ Limited Flexibility: Restricted positioning and animation options
- ❌ Memory Leaks: Potential DOM accumulation issues
🚀 Implementation Guide
Step 1: Import UX Portal Dependencies
import { UxPortalService } from 'libs/ux-portal/src/lib/services/ux-portal.service';
import { UxPortalConfig } from 'libs/ux-portal/src/lib/models/ux-portal.models';
Step 2: Inject UX Portal Service
@Component({
selector: 'your-component',
standalone: true,
// ... other config
})
export class YourComponent {
private readonly portalService = inject(UxPortalService);
// ... rest of component
}
Step 3: Create Modal HTML Generator
private getModalHTML(): string {
return `
<div class="modal-container" style="
min-width: 500px;
max-width: 600px;
background: var(--theme-surface, #ffffff);
border-radius: 16px;
padding: 0;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
">
<!-- Modal Header -->
<div class="modal-header" style="
padding: 2rem 2rem 1rem 2rem;
border-bottom: 1px solid var(--theme-border, #e5e7eb);
background: var(--theme-surface-elevated, #f8fafc);
">
<div style="display: flex; align-items: center; justify-content: space-between;">
<h2 style="margin: 0; font-size: 1.5rem; font-weight: 600; color: var(--theme-text-primary, #1f2937);">
🎯 Modal Title
</h2>
<button onclick="closeModal()" style="
background: transparent;
border: none;
font-size: 1.5rem;
color: var(--theme-text-secondary, #6b7280);
cursor: pointer;
padding: 0.5rem;
border-radius: 8px;
">✕</button>
</div>
</div>
<!-- Modal Content -->
<div class="modal-content" style="padding: 2rem;">
<!-- Your form fields here -->
<div style="margin-bottom: 1.5rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: 500;">Field Label</label>
<input
type="text"
id="modal-input"
placeholder="Enter value"
style="
width: 100%;
padding: 0.75rem;
border: 1px solid var(--theme-border, #d1d5db);
border-radius: 8px;
font-size: 0.875rem;
box-sizing: border-box;
"
oninput="validateForm()"
/>
<div id="error-message" style="
margin-top: 0.5rem;
color: var(--theme-error, #dc2626);
font-size: 0.75rem;
display: none;
"></div>
</div>
</div>
<!-- Modal Actions -->
<div class="modal-actions" style="
padding: 1.5rem 2rem 2rem 2rem;
border-top: 1px solid var(--theme-border, #e5e7eb);
background: var(--theme-surface-elevated, #f8fafc);
display: flex;
gap: 1rem;
">
<button
id="submit-button"
onclick="submitForm()"
disabled
style="
flex: 1;
padding: 0.75rem 1.5rem;
background: var(--theme-primary, #6366f1);
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
opacity: 0.5;
"
>
Submit
</button>
<button onclick="closeModal()" style="
padding: 0.75rem 1.5rem;
background: transparent;
color: var(--theme-text-secondary, #6b7280);
border: 1px solid var(--theme-border, #d1d5db);
border-radius: 8px;
font-weight: 500;
cursor: pointer;
">
Cancel
</button>
</div>
</div>
<script>
console.log('📜 Modal script loaded');
function validateForm() {
const input = document.getElementById('modal-input');
const errorDiv = document.getElementById('error-message');
const submitButton = document.getElementById('submit-button');
if (!input || !errorDiv || !submitButton) {
console.error('❌ Required elements not found');
return false;
}
const value = input.value.trim();
if (!value || value.length < 2) {
errorDiv.textContent = value ? 'Must be at least 2 characters' : 'This field is required';
errorDiv.style.display = 'block';
submitButton.disabled = true;
submitButton.style.opacity = '0.5';
return false;
}
errorDiv.style.display = 'none';
submitButton.disabled = false;
submitButton.style.opacity = '1';
return true;
}
function submitForm() {
if (!validateForm()) return;
const input = document.getElementById('modal-input');
const formData = {
value: input.value.trim()
};
// Call the Angular component method
if (window.yourComponent) {
window.yourComponent.handleFormSubmit(formData);
}
closeModal();
}
function closeModal() {
if (window.uxPortalService) {
window.uxPortalService.closePortal('your-modal-id');
}
}
// Initialize form validation
setTimeout(() => {
const input = document.getElementById('modal-input');
if (input) {
input.addEventListener('input', validateForm);
input.addEventListener('keyup', validateForm);
input.addEventListener('paste', () => setTimeout(validateForm, 10));
validateForm(); // Initial validation
}
}, 100);
</script>
`;
}
Step 4: Open Modal Method
async openModal(): Promise<void> {
try {
console.log('🎯 Opening modal');
const modalHTML = this.getModalHTML();
const portalConfig: UxPortalConfig = {
id: 'your-modal-id',
content: {
type: 'string',
data: modalHTML
},
backdrop: {
type: 'modal',
enabled: true,
color: 'rgba(0, 0, 0, 0.5)',
opacity: 1,
blur: 8,
clickToClose: false
},
container: {
mode: 'modal',
closeOnOutsideClick: false,
closeOnEscape: true,
constrainToViewport: true,
allowOverflow: false,
trapFocus: false,
restoreFocus: true,
autoFocus: false
},
positioning: {
strategy: 'center'
}
};
const result = this.portalService.openPortal(portalConfig);
if (result.success) {
console.log('✅ Modal opened successfully:', result.portalId);
this.setupModalHandlers();
} else {
console.error('❌ Failed to open modal:', result.error);
}
} catch (error: any) {
console.error('Failed to open modal:', error);
}
}
Step 5: Setup Global Handlers
private setupModalHandlers(): void {
// Make the component and portal service available to the modal's JavaScript
(window as any).yourComponent = this;
(window as any).uxPortalService = this.portalService;
}
// Handle form submission
handleFormSubmit(data: { value: string }): void {
console.log('📊 Form submitted:', data);
// Process the form data here
}
🚨 Common Issues and Solutions
Issue 1: Modal Appears Empty
❌ Problem: Modal opens but shows no content
✅ Solution:
- Don't use external HTML files - use inline HTML strings
- Ensure the HTML string is properly formatted
- Check console for JavaScript errors in the embedded script
// ❌ DON'T DO THIS - External file won't load
const response = await fetch('/path/to/modal.html');
// ✅ DO THIS - Inline HTML string
private getModalHTML(): string {
return `<div>Your HTML content here</div>`;
}
Issue 2: Form Validation Not Working
❌ Problem: Submit button never enables, validation doesn't trigger
✅ Solution:
- Set up multiple event listeners for validation
- Add initial validation call after DOM is ready
- Use
setTimeoutto ensure elements are rendered
// ✅ Proper validation setup
setTimeout(() => {
const input = document.getElementById('modal-input');
if (input) {
input.addEventListener('input', validateForm);
input.addEventListener('keyup', validateForm);
input.addEventListener('paste', () => setTimeout(validateForm, 10));
validateForm(); // Initial validation check
}
}, 100);
Issue 3: JavaScript Functions Not Found
❌ Problem: onclick handlers throw "function not defined" errors like validateDatabaseName is not defined
✅ Solution:
- Define functions globally BEFORE opening the portal
- Don't rely on embedded script tags for function definitions
- Set up handlers before creating HTML content
// ❌ DON'T DO THIS - Functions defined in embedded script (timing issues)
const html = `
<input oninput="validateForm()" />
<script>
function validateForm() { /* ... */ }
</script>
`;
// ✅ DO THIS - Define functions globally FIRST
private setupModalHandlers(): void {
// Define functions globally BEFORE opening portal
(window as any).validateForm = () => {
// Your validation logic here
};
(window as any).submitForm = () => {
// Your submit logic here
};
(window as any).closeModal = () => {
this.portalService.closePortal('modal-id');
};
}
async openModal(): Promise<void> {
// Setup handlers FIRST
this.setupModalHandlers();
// Then create and open portal
const html = `<input oninput="validateForm()" />`;
// ... rest of portal config
}
🚨 Critical Timing Issue: HTML elements with onclick or oninput handlers are processed immediately when the portal renders, but embedded <script> tags are processed later. This creates a race condition where the handlers try to call functions that don't exist yet.
Issue 4: Modal Service Errors
❌ Problem: "Modal component not set. Call setModalComponent first."
✅ Solution: Use UX Portal instead of Modal Service
// ❌ DON'T USE - Traditional modal service
this.modalService.openComponent({...});
// ✅ USE THIS - UX Portal system
this.portalService.openPortal({...});
📋 Best Practices Checklist
✅ Do This:
- Use UX Portal service instead of traditional modal services
- Create inline HTML strings, not external files
- Define JavaScript functions globally BEFORE opening portal
- Set up multiple event listeners for form validation
- Use
setTimeoutfor proper initialization timing - Expose component methods to window object for JavaScript access
- Add comprehensive console logging for debugging
- Use theme CSS variables for consistent styling
- Include proper error handling and validation
- Test form validation thoroughly
- Clean up global window references when component destroys
❌ Don't Do This:
- Don't use external HTML files for modal content
- Don't define functions in embedded script tags (timing issues)
- Don't rely on single event listeners for validation
- Don't forget to expose component methods to window
- Don't use traditional modal services that require setup
- Don't skip error handling in form validation
- Don't forget to add
box-sizing: border-boxto inputs - Don't use hardcoded colors - use CSS variables
- Don't forget initial validation calls
- Don't skip console logging for debugging
🎯 Real-World Example: Database Creation Modal
See the Storage Inspector's database creation modal for a complete working example:
// File: apps/ux-test/src/app/storage/inspector/storage-inspector.component.ts
// Method: createNewDatabase()
// HTML Generator: getDatabaseCreationModalHTML()
This implementation demonstrates:
- ✅ Proper form validation with real-time feedback
- ✅ Strategy selection with dynamic descriptions
- ✅ Theme integration with CSS variables
- ✅ Comprehensive error handling
- ✅ Multiple validation event listeners
- ✅ Proper initialization timing
🚀 Advanced Features
Dynamic Content Updates
function updateContent(newData) {
const element = document.getElementById('dynamic-content');
if (element) {
element.innerHTML = newData;
}
}
Multi-Step Forms
let currentStep = 1;
const totalSteps = 3;
function nextStep() {
if (currentStep < totalSteps) {
document.getElementById(`step-${currentStep}`).style.display = 'none';
currentStep++;
document.getElementById(`step-${currentStep}`).style.display = 'block';
}
}
Custom Animations
const portalConfig: UxPortalConfig = {
// ... other config
animation: {
entrance: 'scale',
exit: 'fade',
duration: 300,
enabled: true
}
};
🎉 Summary
The UX Portal system provides a superior alternative to traditional modal services with:
- No setup required - works immediately
- Better performance - optimized rendering and cleanup
- More flexibility - advanced positioning and animations
- Easier debugging - comprehensive logging and error handling
- Theme integration - seamless styling with CSS variables
Follow this guide to create professional, functional modals that integrate seamlessly with your Angular application!