Skip to content

Widget Lifecycle Hooks

Widget lifecycle hooks are methods that are automatically called at different stages of the widget's lifecycle. They allow you to control widget behavior using the interface pattern.

Lifecycle Order

1. Constructor → Widget instance is created
2. OnWidgetInit → Widget initialization operations
3. OnWidgetReady → When template is ready
4. OnWidgetRendered → Rendered to DOM
5. OnWidgetPropertyChanged → Property changes (continuous)
6. OnWidgetDataUpdated → Data updates (continuous)
7. OnWidgetResize → Size changes (continuous)
8. OnWidgetEditModeChanged → Edit mode change (optional)
9. OnWidgetMobileModeChanged → Mobile mode change (optional)
10. OnWidgetDestroy → Widget cleanup

Basic Lifecycle Hooks

OnWidgetInit

Called when the widget is being initialized. Used for initial setup operations.

typescript
import { Widget, xproperty } from '@xcons/widget';
import type { OnWidgetInit } from '@xcons/widget';

@Widget({
  selector: '.init-widget',
  template: `
    <div>
      <p x:text="message">Loading...</p>
      <p>Data Count: <span x:text="dataCount">0</span></p>
    </div>
  `
})
export class InitWidget implements OnWidgetInit {
  @xproperty()
  message: string = '';
  
  @xproperty()
  data: any[] = [];
  
  @xcomputed({ dependencies: ['data'] })
  get dataCount(): number {
    return this.data.length;
  }
  
  async onWidgetInit(): Promise<void> {
    this.safeLog('info', 'Widget is initializing...');
    
    // Initial data loading
    await this.loadInitialData();
    
    this.message = 'Widget ready!';
    this.safeLog('info', 'Widget initialized');
  }
  
  private async loadInitialData(): Promise<void> {
    return new Promise(resolve => {
      setTimeout(() => {
        this.data = [
          { id: 1, name: 'Item 1' },
          { id: 2, name: 'Item 2' }
        ];
        resolve();
      }, 1000);
    });
  }
}

OnWidgetReady

Called when the template and resources are ready.

typescript
import type { OnWidgetReady } from '@xcons/widget';

@Widget({
  selector: '.ready-widget',
  template: `
    <div>
      <h3 x:text="title">Title</h3>
      <p x:text="status">Status</p>
    </div>
  `
})
export class ReadyWidget implements OnWidgetReady {
  @xproperty()
  title: string = '';
  
  @xproperty()
  status: string = '';
  
  onWidgetReady(templateReady: boolean): void {
    this.safeLog('info', 'Widget ready', { templateReady });
    
    if (templateReady) {
      this.title = 'Widget Loaded';
      this.status = 'Template loaded successfully';
      
      // DOM element access is safe
      this.setupDOMElements();
    }
  }
  
  private setupDOMElements(): void {
    // DOM element setup operations
    this.safeLog('debug', 'DOM elements prepared');
  }
}

OnWidgetRendered

Called after the widget is rendered to the DOM.

typescript
import type { OnWidgetRendered } from '@xcons/widget';

@Widget({
  selector: '.rendered-widget',
  template: `
    <div>
      <p>Container: <span x:text="containerInfo">-</span></p>
      <p>Rendered: <span x:text="isRendered ? 'Yes' : 'No'">-</span></p>
    </div>
  `
})
export class RenderedWidget implements OnWidgetRendered {
  @xproperty()
  containerInfo: string = '';
  
  @xproperty()
  isRendered: boolean = false;
  
  onWidgetRendered(container: HTMLElement): void {
    this.safeLog('info', 'Widget rendered');
    
    this.isRendered = true;
    this.containerInfo = `${container.tagName}#${container.id || 'no-id'}`;
    
    // Container-specific operations
    this.performPostRenderOperations(container);
  }
  
  private performPostRenderOperations(container: HTMLElement): void {
    this.safeLog('debug', 'Post-render operations are being performed');
    // Third-party library integrations, measurements, etc.
  }
}

OnWidgetDestroy

Called when the widget is being destroyed. Used for cleanup operations.

typescript
import type { OnWidgetDestroy } from '@xcons/widget';

@Widget({
  selector: '.destroy-widget',
  template: `
    <div>
      <p>Counter: <span x:text="counter">0</span></p>
    </div>
  `
})
export class DestroyWidget implements OnWidgetDestroy {
  @xproperty()
  counter: number = 0;
  
  private intervalId?: number;
  
  onWidgetInit(): void {
    // Start timer
    this.intervalId = window.setInterval(() => {
      this.counter++;
    }, 1000);
    
    // Add event listener
    document.addEventListener('click', this.handleClick);
  }
  
  onWidgetDestroy(): void {
    this.safeLog('info', 'Widget is being cleaned up');
    
    // Clear timer
    if (this.intervalId) {
      clearInterval(this.intervalId);
      this.intervalId = undefined;
    }
    
    // Clear event listener
    document.removeEventListener('click', this.handleClick);
    
    this.safeLog('info', 'Widget cleaned up');
  }
  
  private handleClick = (): void => {
    this.safeLog('debug', 'Click event');
  };
}

Event-Based Lifecycle Hooks

OnWidgetPropertyChanged

Called when a property changes.

typescript
import type { OnWidgetPropertyChanged } from '@xcons/widget';

@Widget({
  selector: '.property-widget',
  template: `
    <div>
      <p>Name: <span x:text="name">-</span></p>
      <p>Age: <span x:text="age">-</span></p>
      <p>Status: <span x:text="status">-</span></p>
      <button x:on:click="updateInfo">Update Info</button>
    </div>
  `
})
export class PropertyWidget implements OnWidgetPropertyChanged {
  @xproperty()
  name: string = '';
  
  @xproperty()
  age: number = 0;
  
  @xproperty()
  status: string = '';
  
  onWidgetPropertyChanged(propertyKey?: string, oldValue?: any, newValue?: any): void {
    this.safeLog('debug', `Property changed: ${propertyKey}`, { oldValue, newValue });
    
    // Specific property check
    if (propertyKey === 'age') {
      this.status = newValue >= 18 ? 'Adult' : 'Child';
    }
    
    if (propertyKey === 'name') {
      this.status = newValue ? 'Valid' : 'Invalid';
    }
  }
  
  updateInfo(): void {
    this.name = 'Ahmet Yılmaz';
    this.age = 25;
  }
}

OnWidgetDataUpdated

Called when data is updated.

typescript
import type { OnWidgetDataUpdated } from '@xcons/widget';

@Widget({
  selector: '.data-widget',
  template: `
    <div>
      <p>Last Updated: <span x:text="lastUpdated">-</span></p>
      <p>Data Count: <span x:text="dataCount">0</span></p>
      <button x:on:click="refreshData">Refresh</button>
    </div>
  `
})
export class DataWidget implements OnWidgetDataUpdated {
  @xproperty()
  data: any[] = [];
  
  @xproperty()
  lastUpdated: string = '';
  
  @xcomputed({ dependencies: ['data'] })
  get dataCount(): number {
    return this.data.length;
  }
  
  onWidgetDataUpdated(): void {
    this.safeLog('info', 'Data updated', { count: this.data.length });
    
    this.lastUpdated = new Date().toLocaleString('en-US');
    
    // Data processing
    this.processData();
  }
  
  private processData(): void {
    this.safeLog('debug', 'Data is being processed...');
  }
  
  refreshData(): void {
    this.data = [
      { id: 1, value: Math.random() },
      { id: 2, value: Math.random() }
    ];
    
    this.onWidgetDataUpdated();
  }
}

OnWidgetResize

Called when the widget size changes.

typescript
import type { OnWidgetResize } from '@xcons/widget';

@Widget({
  selector: '.resize-widget',
  template: `
    <div>
      <p>Width: <span x:text="currentWidth">-</span>px</p>
      <p>Height: <span x:text="currentHeight">-</span>px</p>
      <p x:class:compact="isCompact">Layout: <span x:text="layoutMode">-</span></p>
    </div>
  `
})
export class ResizeWidget implements OnWidgetResize {
  @xproperty()
  currentWidth: number = 0;
  
  @xproperty()
  currentHeight: number = 0;
  
  @xcomputed({ dependencies: ['currentWidth'] })
  get isCompact(): boolean {
    return this.currentWidth < 400;
  }
  
  @xcomputed({ dependencies: ['currentWidth'] })
  get layoutMode(): string {
    return this.currentWidth < 400 ? 'Compact' : 'Normal';
  }
  
  onWidgetResize(): void {
    if (this.xctx) {
      this.currentWidth = this.xctx.width || 0;
      this.currentHeight = this.xctx.height || 0;
      
      this.safeLog('debug', 'Widget size changed', {
        width: this.currentWidth,
        height: this.currentHeight
      });
    }
  }
}

OnWidgetEditModeChanged

Called when edit mode changes.

typescript
import type { OnWidgetEditModeChanged } from '@xcons/widget';

@Widget({
  selector: '.edit-mode-widget',
  template: `
    <div>
      <p>Mode: <span x:text="currentMode">-</span></p>
      <div x:class:visible="isEditMode" class="edit-controls">
        <button>Save</button>
        <button>Cancel</button>
      </div>
    </div>
  `
})
export class EditModeWidget implements OnWidgetEditModeChanged {
  @xproperty()
  currentMode: string = 'View';
  
  @xproperty()
  isEditMode: boolean = false;
  
  onWidgetEditModeChanged(): void {
    this.isEditMode = this.xctx?.isEdit || false;
    this.currentMode = this.isEditMode ? 'Edit' : 'View';
    
    this.safeLog('info', `Edit mode: ${this.currentMode}`);
  }
}

OnWidgetMobileModeChanged

Called when mobile mode changes.

typescript
import type { OnWidgetMobileModeChanged } from '@xcons/widget';

@Widget({
  selector: '.mobile-mode-widget',
  template: `
    <div x:class:mobile="isMobileMode">
      <p>Device: <span x:text="deviceType">-</span></p>
    </div>
  `
})
export class MobileModeWidget implements OnWidgetMobileModeChanged {
  @xproperty()
  deviceType: string = 'Desktop';
  
  @xproperty()
  isMobileMode: boolean = false;
  
  onWidgetMobileModeChanged(): void {
    this.isMobileMode = this.xctx?.isMobile || false;
    this.deviceType = this.isMobileMode ? 'Mobile' : 'Desktop';
    
    this.safeLog('info', `Mobile mode: ${this.isMobileMode}`);
  }
}

Error Handling

OnWidgetInitializationError

Called when an error occurs during initialization.

typescript
import type { OnWidgetInitializationError } from '@xcons/widget';

@Widget({
  selector: '.error-widget',
  template: `
    <div>
      <p x:text="errorMessage">Loading...</p>
    </div>
  `
})
export class ErrorWidget implements OnWidgetInitializationError {
  @xproperty()
  errorMessage: string = '';
  
  async onWidgetInit(): Promise<void> {
    // Error simulation
    throw new Error('Initialization failed!');
  }
  
  onWidgetInitializationError(error: any): void {
    this.safeLog('error', 'Widget could not be initialized', error);
    
    this.errorMessage = `Error: ${error.message}`;
    
    // Recovery attempt
    this.attemptRecovery();
  }
  
  private attemptRecovery(): void {
    setTimeout(() => {
      this.errorMessage = 'Recovered from error';
    }, 2000);
  }
}

Complete Lifecycle Example

A comprehensive example using all hooks together:

```

Best Practices

1. Hook Ordering

typescript
// Organize according to lifecycle order
export class MyWidget implements 
  OnWidgetInit,
  OnWidgetReady,
  OnWidgetRendered,
  OnWidgetDestroy {
  
  onWidgetInit() { }
  onWidgetReady() { }
  onWidgetRendered() { }
  onWidgetDestroy() { }
}

2. Async Operations

typescript
async onWidgetInit(): Promise<void> {
  // Use promises
  await this.loadData();
  await this.setupConfig();
}

3. Cleanup

typescript
onWidgetDestroy(): void {
  // Always perform cleanup
  if (this.subscription) {
    this.subscription.unsubscribe();
  }
  if (this.timer) {
    clearInterval(this.timer);
  }
}

4. Error Handling

typescript
async onWidgetInit(): Promise<void> {
  try {
    await this.riskyOperation();
  } catch (error) {
    this.safeLog('error', 'Init error:', error);
    // Fallback logic
  }
}

Summary

Widget Lifecycle Hooks features:

Lifecycle Control - Manage widget lifecycle
Organized Structure - Organize initialization logic
Resource Management - Manage resources properly
Property Tracking - Track property changes
DOM Optimization - Optimize DOM interactions
Error Handling - Centralize error management
Memory Safety - Prevent memory leaks

The interface pattern makes widget development more organized and maintainable.