Troubleshooting
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
UX Widgets Troubleshooting Guide
This guide addresses common issues that may arise when working with the UX Widgets system and provides detailed solutions to resolve them.
Common Issues
- Circular Update Loops
- Theme Persistence Issues
- Multiple Instance Conflicts
- Storage Initialization Failures
- Performance Degradation
- Component Rendering Issues
Circular Update Loops
Symptoms
- Browser console shows rapid, repeating messages about configuration updates
- Components appear to be constantly refreshing
- High CPU usage
- Browser may become unresponsive
Diagnosis
Circular update loops typically occur when:
- Component A saves a configuration
- Storage service notifies all subscribers about the update
- Component B processes the update and saves its own version
- Storage service notifies all subscribers again
- Component A processes the update and saves its version again
- The cycle continues indefinitely
Solutions
1. Implement Source Tracking
Add a source identifier to all save operations:
// When saving
this.storageService.saveConfig(id, config, items, 'component-a');
// When processing updates
this.storageService.configUpdated$.subscribe(update => {
// Skip self-triggered updates
if (update.source === 'component-a') {
return;
}
// Process the update
});
2. Add Change Detection
Only save when there are actual changes:
// Create a hash of the current state
private getConfigHash(config: any, items: any[]): string {
const data = JSON.stringify({ config, items });
return this.hashString(data);
}
// Before saving
const newHash = this.getConfigHash(config, items);
if (newHash === this.lastSavedHash) {
return; // No changes, don't save
}
// Save and update hash
this.storageService.saveConfig(id, config, items, 'component-a');
this.lastSavedHash = newHash;
3. Implement Update Debouncing
Use RxJS operators to debounce rapid updates:
// Import operators
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
// In component
this.storageService.configUpdated$
.pipe(
debounceTime(300), // Wait 300ms before processing
distinctUntilChanged((prev, curr) => {
// Compare important properties only
return prev.item.config.theme === curr.item.config.theme;
}),
takeUntil(this.destroy$)
)
.subscribe(update => {
// Process the update
});
Theme Persistence Issues
Symptoms
- Theme doesn't persist after page refresh
- Theme changes in one component don't reflect in others
- Multiple themes appear active simultaneously
- Flickering between themes
Diagnosis
Theme persistence issues typically occur when:
- Theme is stored incorrectly or inconsistently
- Theme is applied to wrong elements
- Multiple components are applying conflicting themes
- CSS specificity issues prevent theme application
Solutions
1. Centralize Theme Storage
Store theme in a single, consistent location:
// In storage service
private readonly THEME_KEY = 'ux-widgets-theme';
saveTheme(theme: string): void {
localStorage.setItem(this.THEME_KEY, theme);
}
getTheme(): string {
return localStorage.getItem(this.THEME_KEY) || 'default';
}
2. Separate Component and Global Themes
For components that should manage their own theme vs. global theme:
setThemeClass(theme: string): void {
const dashboardId = this.dashboardConfig().id;
// Remove all theme classes first
this.renderer.removeClass(this.elementRef.nativeElement, 'theme-light');
this.renderer.removeClass(this.elementRef.nativeElement, 'theme-dark');
this.renderer.removeClass(this.elementRef.nativeElement, 'theme-blue');
// Add more theme classes as needed
// Add the new theme class
this.renderer.addClass(this.elementRef.nativeElement, `theme-${theme}`);
// If this is the main dashboard, also apply to body for global effect
if (dashboardId === 'main-ux-dashboard') {
document.body.classList.remove('theme-light', 'theme-dark', 'theme-blue');
document.body.classList.add(`theme-${theme}`);
}
}
3. Use Host Binding for Component Themes
Apply themes directly to component host elements:
import { Component, HostBinding, Input } from '@angular/core';
@Component({
selector: 'app-themed-component',
template: '...'
})
export class ThemedComponent {
@Input() theme: string = 'default';
@HostBinding('class')
get hostClasses(): string {
return `theme-${this.theme}`;
}
}
4. Implement Theme Service with Distinct Scopes
Create a service that manages themes at different scopes:
@Injectable({ providedIn: 'root' })
export class ThemeService {
private globalTheme = signal('default');
private componentThemes = new Map<string, string>();
setGlobalTheme(theme: string): void {
this.globalTheme.set(theme);
document.body.className = `theme-${theme}`;
localStorage.setItem('global-theme', theme);
}
getGlobalTheme(): string {
return this.globalTheme();
}
setComponentTheme(componentId: string, theme: string): void {
this.componentThemes.set(componentId, theme);
localStorage.setItem(`component-theme-${componentId}`, theme);
}
getComponentTheme(componentId: string): string {
return this.componentThemes.get(componentId) || this.globalTheme();
}
}
Multiple Instance Conflicts
Symptoms
- Configurations unexpectedly change when interacting with another component
- Updates from one component override changes from another
- Strange behavior when multiple components of the same type are present
Diagnosis
Multiple instance conflicts occur when:
- Components with the same ID exist simultaneously
- Components incorrectly process updates meant for other instances
- Global resources (like CSS classes on body) are modified by multiple components
Solutions
1. Ensure Unique Component IDs
Generate or enforce unique IDs for each component instance:
// In component
ngOnInit(): void {
// If ID not provided, generate unique one
if (!this.id) {
this.id = `ux-dashboard-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
}
// Initialize with unique ID
this.initialize(this.id);
}
2. Use Component-Specific CSS Classes
Apply styles to component host instead of global elements:
// Before (problematic)
setTheme(theme: string): void {
document.body.className = `theme-${theme}`;
}
// After (improved)
setTheme(theme: string): void {
this.renderer.setAttribute(this.elementRef.nativeElement, 'data-theme', theme);
this.renderer.addClass(this.elementRef.nativeElement, `theme-${theme}`);
}
3. Filter Storage Updates by Component ID
Only process updates relevant to the current component:
this.storageService.configUpdated$
.pipe(
// Only process updates for this component
filter(update => update.item.id === this.componentId),
takeUntil(this.destroy$)
)
.subscribe(update => {
// Process the update
});
Storage Initialization Failures
Symptoms
- Components don't load saved configurations
- Error messages about database access in console
- Configurations don't persist between page refreshes
Diagnosis
Storage initialization failures typically occur when:
- IndexedDB isn't available in the current environment
- Database version conflicts occur
- Database operations are attempted before initialization is complete
- Browser permissions or privacy settings restrict storage access
Solutions
1. Implement Graceful Fallbacks
Fall back to localStorage when IndexedDB isn't available:
initializeStorage(): void {
this.initializeIndexedDB().pipe(
catchError(error => {
console.warn('IndexedDB initialization failed, using localStorage fallback', error);
this.usingFallback = true;
return of(false);
})
).subscribe(initialized => {
this.storageReady.next(true);
});
}
saveConfig(id: string, config: any): Observable<boolean> {
if (this.usingFallback) {
this.saveToLocalStorage(id, config);
return of(true);
}
return this.saveToIndexedDB(id, config);
}
2. Ensure Operations Wait for Initialization
Use a ready signal to delay operations until storage is initialized:
// In storage service
private storageReady = new BehaviorSubject<boolean>(false);
public storageReady$ = this.storageReady.asObservable();
// In component
ngOnInit(): void {
this.storageService.storageReady$
.pipe(
filter(ready => ready), // Wait until ready
take(1), // Only take the first ready signal
takeUntil(this.destroy$)
)
.subscribe(() => {
this.loadConfiguration();
});
}
3. Handle Permissions Issues
Detect and handle permission issues:
private checkStoragePermission(): Observable<boolean> {
return new Observable<boolean>(observer => {
try {
const request = indexedDB.open('permission-test');
request.onsuccess = () => {
request.result.close();
indexedDB.deleteDatabase('permission-test');
observer.next(true);
observer.complete();
};
request.onerror = () => {
observer.next(false);
observer.complete();
};
} catch (error) {
observer.next(false);
observer.complete();
}
});
}
Performance Degradation
Symptoms
- Slow response when interacting with widgets
- High CPU usage
- Browser warnings about long-running scripts
- Increasing page load times
Diagnosis
Performance degradation typically occurs when:
- Too many storage operations are performed in quick succession
- Large configurations are frequently serialized/deserialized
- Inefficient change detection triggers too many updates
- Memory leaks from unmanaged subscriptions
Solutions
1. Batch Storage Operations
Group multiple changes into a single save operation:
// Instead of saving after each small change
onItemMoved(): void {
this.pendingChanges.add('itemsMoved');
this.scheduleConfigSave();
}
onTitleChanged(): void {
this.pendingChanges.add('titleChanged');
this.scheduleConfigSave();
}
private scheduleConfigSave(): void {
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
}
this.saveTimeout = setTimeout(() => {
this.saveConfiguration();
this.pendingChanges.clear();
this.saveTimeout = null;
}, 500);
}
2. Implement Lazy Loading
Load configurations only when needed:
@Component({
selector: 'app-lazy-dashboard',
template: `
<div *ngIf="isVisible">
<app-dashboard [id]="dashboardId"></app-dashboard>
</div>
<button (click)="isVisible = !isVisible">Toggle Dashboard</button>
`
})
export class LazyDashboardComponent {
isVisible = false;
dashboardId = 'my-dashboard';
}
3. Use OnPush Change Detection
Reduce unnecessary change detection:
@Component({
selector: 'app-efficient-widget',
template: '...',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EfficientWidgetComponent {
// Configuration as signals
config = signal<WidgetConfig>({});
constructor(private cdr: ChangeDetectorRef) {}
updateConfig(newConfig: WidgetConfig): void {
this.config.set(newConfig);
this.cdr.markForCheck(); // Manually mark for check
}
}
Component Rendering Issues
Symptoms
- Components display incorrectly
- CSS styling issues
- Elements missing or in wrong positions
- Weird behavior after configuration changes
Diagnosis
Rendering issues typically occur when:
- Components render before configurations are loaded
- CSS conflicts between multiple instances
- Third-party library conflicts
- Zone.js issues with async operations
Solutions
1. Use Loading States
Show loading indicators until data is ready:
@Component({
selector: 'app-loading-widget',
template: `
<div *ngIf="isLoading" class="loading-indicator">Loading...</div>
<div *ngIf="!isLoading" class="widget-content">
<!-- Widget content -->
</div>
`
})
export class LoadingWidgetComponent {
isLoading = true;
ngOnInit(): void {
this.loadConfiguration().pipe(
finalize(() => this.isLoading = false)
).subscribe();
}
}
2. Apply CSS Encapsulation
Use Angular's view encapsulation or CSS modules:
@Component({
selector: 'app-encapsulated-widget',
template: '...',
styles: [`
:host {
display: block;
position: relative;
}
/* Component-specific styles */
`],
encapsulation: ViewEncapsulation.Emulated
})
export class EncapsulatedWidgetComponent {
// Component logic
}
3. Ensure Zone.js Integration
Run operations inside the Angular zone when needed:
private updateOutsideAngular(): void {
this.ngZone.runOutsideAngular(() => {
// Expensive operation that doesn't need to trigger change detection
this.performExpensiveCalculation();
// When done, run inside Angular zone if UI update needed
this.ngZone.run(() => {
this.updateComplete = true;
});
});
}
IndexedDB Troubleshooting
Common IndexedDB Errors and Solutions
1. QuotaExceededError
Error: QuotaExceededError: The current transaction has exceeded its quota limitations.
Solution:
- Reduce the size of saved configurations
- Implement data pruning for old/unused configurations
- Request increased storage quota:
navigator.storage.persist().then(persistent => {
if (persistent) {
console.log('Storage will not be cleared except by explicit user action');
} else {
console.log('Storage may be cleared by the UA under storage pressure');
}
});
2. VersionError
Error: VersionError: The requested database version is less than the existing database version.
Solution:
- Ensure consistent database version across code:
private readonly DATABASE_VERSION = 1; // Must be consistent
// When upgrading, increment this number and handle migrations:
private readonly DATABASE_VERSION = 2;
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
const oldVersion = event.oldVersion;
if (oldVersion < 1) {
// Create initial schema
}
if (oldVersion < 2) {
// Migrate data for version 2
}
};
3. InvalidStateError
Error: InvalidStateError: The object is already closed.
Solution:
- Ensure database connections are properly managed:
private getTransaction(db: IDBDatabase, mode: IDBTransactionMode): IDBTransaction {
// Check if database is still open
if (db.version === 0) {
throw new Error('Database connection closed');
}
return db.transaction(this.STORE_NAME, mode);
}
Debugging Tools and Techniques
1. Storage Inspection
View and modify stored configurations:
- Open DevTools (F12)
- Go to Application tab
- Under Storage, check:
- IndexedDB
- Local Storage
- Session Storage
2. Add Debug Logging
Enable verbose logging for troubleshooting:
@Injectable({ providedIn: 'root' })
export class LoggingService {
private debugEnabled = false;
enableDebug(): void {
this.debugEnabled = true;
localStorage.setItem('debug-enabled', 'true');
}
debug(source: string, message: string, data?: any): void {
if (!this.debugEnabled) return;
console.group(`[${source}] ${message}`);
if (data) console.log(data);
console.groupEnd();
}
}
3. Configuration Auditing
Track all configuration changes:
saveConfig(id: string, config: any, source: string): Observable<boolean> {
// Log all save operations
console.log(`Saving config for ${id} from ${source}`, config);
return this.actualSaveOperation(id, config, source).pipe(
tap(success => {
console.log(`Save result for ${id}: ${success ? 'SUCCESS' : 'FAILED'}`);
})
);
}
Emergency Recovery
1. Clear Storage Data
If the application gets into a corrupted state:
clearAllStorage(): void {
// Clear IndexedDB
indexedDB.deleteDatabase(this.DATABASE_NAME);
// Clear localStorage keys related to our app
Object.keys(localStorage).forEach(key => {
if (key.startsWith(this.STORAGE_PREFIX)) {
localStorage.removeItem(key);
}
});
// Reload application
window.location.reload();
}
2. Restore Default Configurations
Provide a way to reset to defaults:
restoreDefaultConfigurations(): void {
const defaultConfigs = this.getDefaultConfigurations();
// Save all default configurations
from(defaultConfigs).pipe(
concatMap(config => this.storageService.saveConfig(config.id, config, 'defaults-reset'))
).subscribe({
complete: () => {
console.log('Default configurations restored');
window.location.reload();
}
});
}
3. Provide Recovery Mode
Add a special URL parameter to enable recovery mode:
ngOnInit(): void {
// Check for recovery mode
const urlParams = new URLSearchParams(window.location.search);
const recoveryMode = urlParams.get('recovery');
if (recoveryMode === 'true') {
this.enterRecoveryMode();
} else {
this.normalInitialization();
}
}
private enterRecoveryMode(): void {
console.log('RECOVERY MODE ENABLED');
// Show recovery UI
this.recoveryModeActive = true;
// Disable auto-loading of potentially corrupted configurations
this.autoLoad = false;
}
Conclusion
When troubleshooting UX Widgets, follow these steps:
- Identify the symptom - Understand exactly what's not working
- Check the console - Look for error messages or warnings
- Inspect storage - Verify configurations are saving and loading correctly
- Add debug logging - If needed, enable verbose logging to track interactions
- Isolate the issue - Test components in isolation to pinpoint problems
- Apply targeted fixes - Use the appropriate solution from this guide
- Verify the fix - Test thoroughly to ensure the issue is resolved
By following the techniques in this guide, you can diagnose and resolve most issues that arise when working with the UX Widgets system.