Vue typescript class and decorator based component system with support for ES class inheritance and Vue 3 composition API integration.
—
Decorators for watchers, event emitters, dependency injection, and lifecycle hooks in Vue components.
Decorator for reactive data watchers with comprehensive configuration options.
/**
* Decorator for reactive data watchers
* @param key - The reactive property or path to watch
* @param options - Optional watcher configuration
* @returns Method decorator
*/
function Watch(key: string, options?: WatchOptions): MethodDecorator;
interface WatchOptions {
flush?: 'post';
deep?: boolean;
immediate?: boolean;
}
interface WatchConfig {
key: string;
handler: WatchCallback;
flush?: 'post';
deep?: boolean;
immediate?: boolean;
}Usage Examples:
import { Component, Setup, Watch } from "vue-facing-decorator";
import { ref, reactive } from "vue";
@Component
class WatchComponent {
@Setup(() => ref("initial"))
message!: string;
@Setup(() => ref(0))
count!: number;
@Setup(() => reactive({ nested: { value: "deep" } }))
deepObject!: { nested: { value: string } };
// Basic watcher
@Watch("message")
onMessageChange(newVal: string, oldVal: string) {
console.log(`Message changed from "${oldVal}" to "${newVal}"`);
}
// Immediate watcher
@Watch("count", { immediate: true })
onCountChange(newVal: number, oldVal: number) {
console.log(`Count: ${newVal} (was: ${oldVal})`);
}
// Deep watcher for objects
@Watch("deepObject", { deep: true })
onDeepObjectChange(newVal: any, oldVal: any) {
console.log("Deep object changed:", newVal, oldVal);
}
// Post-flush watcher (after DOM updates)
@Watch("message", { flush: "post" })
onMessageChangePost(newVal: string) {
// This runs after DOM updates
console.log("DOM updated with new message:", newVal);
}
// Multiple watchers on same property
@Watch("count")
firstCountWatcher(newVal: number) {
console.log("First watcher:", newVal);
}
@Watch("count", { immediate: true })
secondCountWatcher(newVal: number) {
console.log("Second watcher:", newVal);
}
}Decorator for event emission methods with custom event names.
/**
* Decorator for event emission methods
* @param eventName - Optional custom event name, defaults to method name
* @returns Method decorator
*/
function Emit(eventName?: string): MethodDecorator;
type EmitConfig = null | string;Usage Examples:
import { Component, Emit, Setup } from "vue-facing-decorator";
import { ref } from "vue";
@Component
class EmitComponent {
@Setup(() => ref("Hello"))
message!: string;
// Basic emit - event name matches method name
@Emit()
clicked() {
return { timestamp: Date.now() };
}
// Custom event name
@Emit("customEvent")
handleAction() {
return this.message;
}
// Emit without return value
@Emit("simpleEvent")
simpleAction() {
console.log("Action performed");
}
// Async emit
@Emit("asyncEvent")
async asyncAction() {
await new Promise(resolve => setTimeout(resolve, 1000));
return { result: "async complete" };
}
// Emit with parameters
@Emit("dataChange")
changeData(newData: any) {
this.message = newData.message;
return newData;
}
// Multiple emits from same method
@Emit("start")
@Emit("process")
complexAction() {
return { action: "complex", data: this.message };
}
}
// Usage in parent:
// <EmitComponent
// @clicked="onClicked"
// @customEvent="onCustom"
// @simpleEvent="onSimple"
// @asyncEvent="onAsync"
// @dataChange="onDataChange"
// />Decorator for providing reactive data to descendant components.
/**
* Decorator for providing reactive data to descendant components
* @param key - Optional provide key, defaults to property name
* @returns Property decorator
*/
function Provide(key?: string): PropertyDecorator;
type ProvideConfig = null | string;Usage Examples:
import { Component, Provide, Setup } from "vue-facing-decorator";
import { ref, reactive } from "vue";
@Component
class ProvideComponent {
// Provide with default key (property name)
@Provide()
@Setup(() => ref("provided value"))
sharedData!: string;
// Provide with custom key
@Provide("customKey")
@Setup(() => reactive({ theme: "dark", lang: "en" }))
appConfig!: { theme: string; lang: string };
// Provide computed value
@Provide("computedValue")
@Setup(() => computed(() => `Config: ${this.appConfig.theme}/${this.appConfig.lang}`))
configSummary!: string;
// Provide method
@Provide()
updateTheme(newTheme: string) {
this.appConfig.theme = newTheme;
}
// Provide static value
@Provide("staticKey")
staticValue = "never changes";
}Decorator for injecting provided data from ancestor components.
/**
* Decorator for injecting provided data from ancestor components
* @param config - Optional injection configuration
* @returns Property decorator
*/
function Inject(config?: InjectConfig): PropertyDecorator;
interface InjectConfig {
from?: string | symbol | Symbol | InjectionKey<any>;
default?: any;
}Usage Examples:
import { Component, Inject } from "vue-facing-decorator";
import { InjectionKey } from "vue";
// Define injection keys for type safety
const ThemeKey: InjectionKey<string> = Symbol('theme');
const ConfigKey: InjectionKey<{theme: string, lang: string}> = Symbol('config');
@Component
class InjectComponent {
// Basic inject - uses property name as key
@Inject()
sharedData!: string;
// Inject with custom key
@Inject({ from: "customKey" })
appConfig!: { theme: string; lang: string };
// Inject with default value
@Inject({ from: "maybeUndefined", default: "default value" })
optionalValue!: string;
// Inject with symbol key for type safety
@Inject({ from: ThemeKey })
theme!: string;
@Inject({ from: ConfigKey, default: () => ({ theme: "light", lang: "en" }) })
config!: { theme: string; lang: string };
// Inject method
@Inject({ from: "updateTheme" })
updateTheme!: (theme: string) => void;
mounted() {
console.log("Injected data:", this.sharedData);
console.log("App config:", this.appConfig);
// Call injected method
if (this.updateTheme) {
this.updateTheme("light");
}
}
}Decorator for explicitly marking methods as lifecycle hooks.
/**
* Decorator for explicitly marking methods as lifecycle hooks
* @returns Method decorator
*/
function Hook(): MethodDecorator;
type HookConfig = null;Usage Examples:
import { Component, Hook, Setup } from "vue-facing-decorator";
import { ref } from "vue";
@Component
class HookComponent {
@Setup(() => ref("component data"))
data!: string;
// Explicit hook decoration (optional - hooks are auto-detected by name)
@Hook()
mounted() {
console.log("Component mounted");
}
@Hook()
beforeUnmount() {
console.log("About to unmount");
}
// Regular lifecycle hooks (auto-detected, no decorator needed)
created() {
console.log("Component created");
}
updated() {
console.log("Component updated");
}
// Custom method that happens to have hook name but isn't a hook
@Hook()
customMounted() {
// This will be treated as a regular method, not a lifecycle hook
console.log("Custom method with hook-like name");
}
}All Vue 3 lifecycle hooks are automatically supported by name:
// Vue 3 Lifecycle Hooks (all supported automatically)
interface LifecycleHooks {
beforeCreate?(): void;
created?(): void;
beforeMount?(): void;
mounted?(): void;
beforeUpdate?(): void;
updated?(): void;
activated?(): void;
deactivated?(): void;
beforeUnmount?(): void;
unmounted?(): void;
renderTracked?(): void;
renderTriggered?(): void;
errorCaptured?(): void;
serverPrefetch?(): void;
render?(): VNode;
}
// Legacy Vue 2 hooks (still supported)
interface LegacyHooks {
beforeDestroy?(): void;
destroyed?(): void;
}Complete Lifecycle Example:
import { Component, Setup, Watch, Emit, Provide, Inject } from "vue-facing-decorator";
import { ref } from "vue";
@Component
class CompleteLifecycleComponent {
@Setup(() => ref(0))
counter!: number;
@Provide()
@Setup(() => ref("provided from parent"))
sharedValue!: string;
@Inject({ from: "parentData", default: "no parent" })
parentData!: string;
// All lifecycle hooks
beforeCreate() {
console.log("Before create - component instance being created");
}
created() {
console.log("Created - component instance created");
}
beforeMount() {
console.log("Before mount - about to mount");
}
mounted() {
console.log("Mounted - component mounted to DOM");
}
beforeUpdate() {
console.log("Before update - reactive data changed, about to update");
}
updated() {
console.log("Updated - DOM updated");
}
beforeUnmount() {
console.log("Before unmount - about to unmount");
}
unmounted() {
console.log("Unmounted - component unmounted");
}
// Watcher
@Watch("counter")
onCounterChange(newVal: number, oldVal: number) {
this.counterChanged(newVal, oldVal);
}
// Emitter
@Emit("counterChanged")
counterChanged(newVal: number, oldVal: number) {
return { newVal, oldVal, timestamp: Date.now() };
}
increment() {
this.counter++;
}
}Install with Tessl CLI
npx tessl i tessl/npm-vue-facing-decorator