CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vue-property-decorator

TypeScript decorators for Vue.js class-based components with property binding, state management, and lifecycle capabilities

Pending
Overview
Eval results
Files

lifecycle-events.mddocs/

Lifecycle and Events

Decorators for handling component lifecycle events and custom event emission. These decorators provide reactive data observation and automatic event emission patterns for Vue class-based components.

Capabilities

Watch Decorator

Creates reactive watchers for data properties, enabling components to respond to changes in data, props, or computed properties.

/**
 * Creates reactive watchers for data properties
 * @param path - Property path or expression to observe (required)
 * @param options - WatchOptions object (optional, defaults to {})
 * @returns Method decorator function
 */
function Watch(path: string, options: WatchOptions = {}): MethodDecorator;

interface WatchOptions {
  deep?: boolean;
  immediate?: boolean;
}

Usage Examples:

import { Vue, Component, Watch, Prop } from "vue-property-decorator";

@Component
export default class MyComponent extends Vue {
  @Prop()
  count!: number;

  localData = 0;
  user = { name: "", age: 0 };

  // Basic watcher
  @Watch("count")
  onCountChanged(newVal: number, oldVal: number) {
    console.log(`count changed from ${oldVal} to ${newVal}`);
  }

  // Deep watcher for objects
  @Watch("user", { deep: true })
  onUserChanged(newUser: any, oldUser: any) {
    console.log("User object changed:", newUser);
  }

  // Immediate watcher (executes on component creation)
  @Watch("localData", { immediate: true })
  onLocalDataChanged(newVal: number, oldVal: number) {
    if (newVal > 10) {
      this.$emit("threshold-exceeded", newVal);
    }
  }

  // Watch nested properties
  @Watch("user.name")
  onUserNameChanged(newName: string, oldName: string) {
    console.log(`User name changed from ${oldName} to ${newName}`);
  }

  // Watch computed properties
  get fullName() {
    return `${this.user.name} (${this.user.age})`;
  }

  @Watch("fullName")
  onFullNameChanged(newFullName: string) {
    console.log("Full name updated:", newFullName);
  }
}

Emit Decorator

Automatically emits events with method return values and arguments, providing a declarative way to handle component event emission.

/**
 * Automatically emits events with method return values and arguments
 * @param event - Event name (optional, defaults to kebab-case method name)
 * @returns Method decorator function
 */
function Emit(event?: string): MethodDecorator;

Usage Examples:

import { Vue, Component, Emit } from "vue-property-decorator";

@Component
export default class MyComponent extends Vue {
  count = 0;

  // Basic emit - event name defaults to kebab-case method name
  @Emit()
  increment() {
    this.count++;
    return this.count; // Emitted as first argument
  }

  // Custom event name
  @Emit("custom-event")
  handleCustomAction() {
    return { timestamp: Date.now(), action: "custom" };
  }

  // Emit with method arguments
  @Emit("user-updated")
  updateUser(name: string, age: number) {
    // Method arguments are emitted after return value
    return { success: true }; // Emitted as [{ success: true }, "name", age]
  }

  // Emit without return value
  @Emit("button-clicked")
  handleClick() {
    // No return value, only method arguments are emitted
    console.log("Button was clicked");
  }

  // Emit with Promise return value
  @Emit("async-complete")
  async performAsyncAction() {
    const result = await this.fetchData();
    return result; // Promise result is awaited and then emitted
  }

  // Complex emit with multiple arguments
  @Emit("data-processed")
  processData(input: any[], options: any) {
    const processed = input.map(item => ({ ...item, processed: true }));
    return { 
      processed, 
      originalCount: input.length, 
      processedCount: processed.length 
    };
    // Emits: [returnValue, input, options]
  }

  private async fetchData() {
    // Simulate async operation
    return new Promise(resolve => {
      setTimeout(() => resolve({ data: "fetched" }), 1000);
    });
  }
}

Advanced Usage Patterns

Form Validation with Watchers

import { Vue, Component, Watch, Prop } from "vue-property-decorator";

@Component
export default class ValidationForm extends Vue {
  @Prop({ required: true })
  initialData!: any;

  formData = {
    email: "",
    password: "",
    confirmPassword: ""
  };

  errors: any = {};

  @Watch("formData.email")
  validateEmail(email: string) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    this.errors.email = emailRegex.test(email) ? null : "Invalid email format";
  }

  @Watch("formData.password")
  validatePassword(password: string) {
    this.errors.password = password.length >= 8 ? null : "Password must be at least 8 characters";
    // Re-validate confirm password when password changes
    this.validateConfirmPassword(this.formData.confirmPassword);
  }

  @Watch("formData.confirmPassword")
  validateConfirmPassword(confirmPassword: string) {
    this.errors.confirmPassword = 
      confirmPassword === this.formData.password ? null : "Passwords do not match";
  }

  @Watch("initialData", { immediate: true, deep: true })
  loadInitialData(data: any) {
    if (data) {
      this.formData = { ...data };
    }
  }
}

Event Chain with Emit

import { Vue, Component, Emit, Watch } from "vue-property-decorator";

@Component
export default class DataProcessor extends Vue {
  rawData: any[] = [];
  processing = false;

  @Watch("rawData")
  onDataChanged(newData: any[]) {
    if (newData.length > 0) {
      this.startProcessing();
    }
  }

  @Emit("processing-started")
  startProcessing() {
    this.processing = true;
    return { timestamp: Date.now(), itemCount: this.rawData.length };
  }

  @Emit("item-processed")
  processItem(item: any, index: number) {
    // Simulate processing
    const processed = { ...item, processed: true, index };
    return processed;
  }

  @Emit("processing-complete")
  completeProcessing() {
    this.processing = false;
    return {
      timestamp: Date.now(),
      totalProcessed: this.rawData.length,
      success: true
    };
  }

  @Emit("processing-error")
  handleProcessingError(error: Error) {
    this.processing = false;
    return { error: error.message, timestamp: Date.now() };
  }
}

Real-time Data Synchronization

import { Vue, Component, Watch, Emit } from "vue-property-decorator";

@Component
export default class RealTimeSync extends Vue {
  localData: any = {};
  syncEnabled = true;
  lastSyncTime = 0;

  @Watch("localData", { deep: true })
  onLocalDataChanged(newData: any, oldData: any) {
    if (this.syncEnabled && this.hasSignificantChanges(newData, oldData)) {
      this.scheduleSync();
    }
  }

  @Watch("syncEnabled")
  onSyncToggled(enabled: boolean) {
    if (enabled) {
      this.performSync();
    }
  }

  @Emit("sync-requested")
  scheduleSync() {
    // Debounce sync requests
    clearTimeout(this.syncTimer);
    this.syncTimer = setTimeout(() => {
      this.performSync();
    }, 1000);
    return { data: this.localData, scheduled: true };
  }

  @Emit("sync-complete")
  async performSync() {
    try {
      await this.syncToServer(this.localData);
      this.lastSyncTime = Date.now();
      return { success: true, timestamp: this.lastSyncTime };
    } catch (error) {
      this.handleSyncError(error);
    }
  }

  @Emit("sync-error")
  handleSyncError(error: any) {
    return { error: error.message, timestamp: Date.now() };
  }

  private syncTimer: any;

  private hasSignificantChanges(newData: any, oldData: any): boolean {
    // Implementation for determining significant changes
    return JSON.stringify(newData) !== JSON.stringify(oldData);
  }

  private async syncToServer(data: any): Promise<void> {
    // Implementation for server synchronization
    return new Promise((resolve) => {
      setTimeout(resolve, 500); // Simulate network delay
    });
  }
}

Types

interface WatchOptions {
  deep?: boolean;
  immediate?: boolean;
}

type MethodDecorator = (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;

Install with Tessl CLI

npx tessl i tessl/npm-vue-property-decorator

docs

base-components.md

component-properties.md

dependency-injection.md

index.md

lifecycle-events.md

tile.json