Skip to content

Example Usage

Using ivue is very simple, but you need to understand a few principles. See the examples below expanding on each principle.

Basic Usage

Classic Counter example built with ivue

vue
<script setup lang="ts">
import { ivue, iref } from 'ivue';

class Counter {
  count = iref(0);
  increment() {
    this.count++;
  }
}

const counter = ivue(Counter);
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }}
</template>
For this example we initialize the component like this:
vue
<template>
  <CounterBasic />
</template>
Result
Increment Count: 0

NOTICE: JavaScript Class Caveat

See () => counter.increment(). You cannot simply use counter.increment to refer to the method when it was defined as an object of a class, because that method would not know that it has to be bound to counter. You have to either use () => counter.increment() or counter.increment.bind(counter), but the latter example is too verbose, so the preferred more readable way is to use an arrow function.

Using Props

See the highlighted sections related to props.

vue
<script setup lang="ts">
import { ivue, iref } from 'ivue';

interface CounterProps {
  initialCount: number;
}
const props = defineProps<CounterProps>();

class Counter {
  constructor(public props: CounterProps) {
    this.count = this.props.initialCount;
  }
  count = iref(0);
  increment() {
    this.count++;
  }
}

/**
 * NOTICE that: ivue(ClassName, ...args) uses TypeScript to infer
 * the correct argument types that you need to pass to the constructor.
 */
const counter = ivue(Counter, props);
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }}
</template>
For this example we initialize the component like this:
vue
<template>
  <CounterWithProps :initial-count="5" />
</template>
Result
Increment Count: 5

Using Emits

See the highlighted sections related to using emits.

vue
<script setup lang="ts">
import { ivue, iref } from 'ivue';

interface CounterProps {
  initialCount: number;
}

interface CounterEmits {
  (e: 'increment', count: number): void;
}

const props = defineProps<CounterProps>();
const emit = defineEmits<CounterEmits>();

class Counter {
  constructor(public props: CounterProps, public emit: CounterEmits) {
    this.count = this.props.initialCount;
  }
  count = iref(0);
  increment() {
    this.count++;
    this.emit('increment', this.count);
  }
}

/**
 * NOTICE that: ivue(ClassName, ...args) uses TypeScript to infer
 * the correct argument types that you need to pass to the constructor.
 */
const counter = ivue(Counter, props, emit);
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }}
</template>
For this example we initialize the component like this:
vue
<script setup lang="ts">
function onIncrement(value: number) {
  alert('Count is now: ' + value);
}
</script>
<template>
  <CounterWithPropsAndEmits :initial-count="5" @increment="onIncrement" />
</template>
Result
Increment Count: 5

Using Refs

Refs defined externally in the component outside of the class.

vue
<script setup lang="ts">
import { ref } from 'vue';
import { ivue, iref, iuse } from 'ivue';

type SpanRef = HTMLElement | null;

class Counter {
  constructor(public span: SpanRef) {}
  count = iref(0);
  increment() {
    this.count++;
    (this.span as HTMLElement).innerHTML = String(this.count + 1);
  }
}

const span = ref<SpanRef>(null);
const counter = ivue(Counter, iuse(span));

defineExpose<Counter>(counter);
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }}
  <span ref="span"></span>
</template>
For this example we initialize the component like this:
vue
<template>
  <CounterExternalRefs />
</template>
Result
Increment Count: 0

Refs defined internally inside of the class.

vue
<script setup lang="ts">
import { ivue, iref } from 'ivue';

class Counter {
  count = iref(0);
  span = iref<HTMLElement | null>(null);
  increment() {
    this.count++;
    (this.span as HTMLElement).innerHTML = String(this.count + 1);
  }
}

const counter = ivue(Counter);
const { span } = counter.toRefs(['span']);
/**
 * Or you can use `const { span } = counter.toRefs();`
 * but `const { span } = counter.toRefs(['span']);` is more performant
 * because we are only looping through 1 property instead of all of them.
 */
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }}
  <span ref="span"></span>
</template>
For this example we initialize the component like this:
vue
<template>
  <CounterInternalRefs />
</template>
Result
Increment Count: 0

Using Define Expose

Use class as interface for defineExpose()

See the highlighted sections related to defineExpose.

vue
<script setup lang="ts">
import { ivue } from 'ivue';

import Counter from './CounterDefineExposeClass'

const counter = ivue(Counter);

defineExpose<Counter>(counter);
</script>
<template>
  Count: {{ counter.count }}
</template>
ts
import { iref } from 'ivue';

export default class Counter {
  count = iref(0);
  increment() {
    this.count++;
  }
  decrement() {
    this.count--;
  }
}
For this example we initialize the component like this:
vue
<script setup lang="ts">
const defineExposeRef = ref<CounterDefineExposeClass | null>(null);

function increment() {
  defineExposeRef.value.increment();
}

function decrement() {
  defineExposeRef.value.decrement();
}
</script>
<template>
  <button @click="increment">
    Increment via Component Ref from Parent Component</button
  ><br />
  <button @click="decrement">
    Decrement via Component Ref from Parent Component
  </button>
  <CounterDefineExpose />
</template>
Result


Count: 0

Pick allowed interface properties for defineExpose()

vue
<script setup lang="ts">
import { ivue, iref } from 'ivue';

/** 
 * See that only 'count', 'increment' properties are picked 
 * for the define expose interface. 
 */
export type CounterExposed = Pick<Counter, 'count' | 'increment'>;

class Counter {
  count = iref(0);
  increment() {
    this.count++;
  }
  decrement() {
    this.count--;
  }
}

const counter = ivue(Counter);

defineExpose<CounterExposed>(counter);
</script>
<template>Count: {{ counter.count }}</template>

Using Composables

Assign Vue 3 Composable to a class property

See the highlighted sections related to composable usage.

vue
<script setup lang="ts">
import { useMouse, usePointer } from '@vueuse/core';
import { ivue, iref, iuse } from 'ivue';

class Counter {
  count = iref(0);
  increment() {
    this.count++;
  }
  mouse = iuse(useMouse);
  pointer = iuse(usePointer);
}

const counter = ivue(Counter);
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }} <br />
  <br />
  Mouse: <br />
  X: {{ counter.mouse.x }} <br />
  Y: {{ counter.mouse.y }} <br />
  <br />
  Pointer: <br />
  X: {{ counter.pointer.x }} <br />
  Y: {{ counter.pointer.y }} <br />
</template>
For this example we initialize the component like this:
vue
<template>
  <CounterComposables />
</template>
Result
Increment Count: 0

Mouse:
X: 0
Y: 0

Pointer:
X: 0
Y: 0

Destructure Vue 3 Composable into the class

See the highlighted sections related to destructuring composable usage.

vue
<script setup lang="ts">
import { useCustomMouse } from './functions/useCustomMouse';

import { ivue, iuse, iref, type Use } from 'ivue';

/**
 * Use the ivue Utility Type: Use<typeof YourComposableFunctionName>
 * to get he resulting unwrapped composable properties and functions.
 */
type UseCustomMouse = Use<typeof useCustomMouse>;

class Counter {
  count = iref(0);

  increment() {
    this.count++;
  }

  /**
   * 'x', 'y', 'sum', 'total' are Refs that will be unwrapped to their bare raw types and destructured into the class.
   * Even though unwrapped (de-Refed), they will maintain their behavior as Refs and thus will maintain reactivity
   * and at the same time get destructured into this class root level scope because
   * Vue 3's `reactive()` Proxy will be able to resolve those Refs internally.
   */
  x: UseCustomMouse['x']; // Unwrapped Ref<number> becomes -> number
  y: UseCustomMouse['y']; // Unwrapped Ref<number> becomes -> number

  sum: UseCustomMouse['sum']; // 'sum' method that will be destructured into this class on construct()
  total: UseCustomMouse['total']; // 'total' computed Ref that will also be destructured into this class on construct()

  constructor() {
    ({
      x: this.x,
      y: this.y,
      sum: this.sum,
      total: this.total,
    } = iuse(useCustomMouse, 5));
  }
}

const counter = ivue(Counter);
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }} <br />
  Mouse X: {{ counter.x }}, Y: {{ counter.y }}
  <br />
  Total (computed): {{ counter.total }}
  <br />
  <button class="button" @click="() => counter.sum()">
    Click This Big Sum Button To Total X + Y
  </button>
</template>
ts
import { computed, ref } from 'vue';
import { useMouse } from '@vueuse/core';

export function useCustomMouse(requiredProp: number) {
  const { x, y } = useMouse();

  const _sum = ref(0);

  function sum() {
    _sum.value = x.value + y.value + requiredProp;
  }

  const total = computed(() => {
    // Returning without .value, not entirely correctly, but will still work in Vue template.
    // This is to test the impressive IVue unwrapping capabilities of deeply nested and confusing Refs
    return _sum;
  });

  return {
    x,
    y,
    sum,
    total,
  };
}
For this example we initialize the component like this:
vue
<template>
  <CounterComposablesDestructuring />
</template>
Result
Increment Count: 0
Mouse X: 0, Y: 0
Total (computed): 0

Assign ivue Composables to a class property

Using ivue based composables assigned to a class property:

vue
<script setup lang="ts">
import { CustomMouse } from './classes/CustomMouse';

import { ivue, iref } from 'ivue';

class Counter {
  count = iref(0);

  increment() {
    this.count++;
  }

  mouse = ivue(CustomMouse, 5);
}

const counter = ivue(Counter);
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }} <br />
  Mouse X: {{ counter.mouse.x }}, Y: {{ counter.mouse.y }}
  <br />
  Total (computed): {{ counter.mouse.total }}
  <br />
  <button class="button" @click="() => counter.mouse.sum()">
    Click This Big Sum Button To Total X + Y
  </button>
</template>
ts
import { useMouse } from '@vueuse/core';
import { iref, iuse, type Use } from 'ivue';

type UseMouse = Use<typeof useMouse>;

export class CustomMouse {
  x: UseMouse['x'];
  y: UseMouse['y'];

  _sum = iref(0);

  constructor(public requiredProp: number) {
    ({ x: this.x, y: this.y } = iuse(useMouse));
  }

  sum() {
    this._sum = this.x + this.y + this.requiredProp;
  }

  get total() {
    return this._sum;
  }
}
For this example we initialize the component like this:
vue
<template>
  <CounterComposablesIvue />
</template>
Result
Increment Count: 0
Mouse X: 0, Y: 0
Total (computed): 0

Destructure ivue Composables Into Class Scope

See the sections related to using ivue based composables initialized through class destructuring assignment.

vue
<script setup lang="ts">
import { CustomMouse } from './classes/CustomMouse';

import { ivue, iref } from 'ivue';

class Counter {
  count = iref(0);

  increment() {
    this.count++;
  }

  /**
   * 'x', 'y', 'total' are Refs that will be unwrapped to their bare raw types and destructured into the class.
   * Even though unwrapped (de-Refed), they will maintain their behavior as Refs and thus will maintain reactivity
   * and at the same time get destructured into this class root level scope because
   * Vue 3's `reactive()` Proxy will be able to resolve those Refs internally.
   */
  x: CustomMouse['x']; // Unwrapped Ref<number> becomes -> number
  y: CustomMouse['y']; // Unwrapped Ref<number> becomes -> number
  total: CustomMouse['total']; // Unwrapped ComputedRef<number> becomes -> number
  sum: CustomMouse['sum']; // Function remains a function

  constructor() {
    ({
      x: this.x,
      y: this.y,
      sum: this.sum,
      total: this.total,
    } = ivue(CustomMouse, 5).toRefs(true));
  }
}

const counter = ivue(Counter);
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }} <br />
  Mouse X: {{ counter.x }}, Y: {{ counter.y }}
  <br />
  Total (computed): {{ counter.total }}
  <br />
  <button class="button" @click="() => counter.sum()">
    Click This Big Sum Button To Total X + Y
  </button>
</template>
ts
import { useMouse } from '@vueuse/core';
import { iref, iuse, type Use } from 'ivue';

type UseMouse = Use<typeof useMouse>;

export class CustomMouse {
  x: UseMouse['x'];
  y: UseMouse['y'];

  _sum = iref(0);

  constructor(public requiredProp: number) {
    ({ x: this.x, y: this.y } = iuse(useMouse));
  }

  sum() {
    this._sum = this.x + this.y + this.requiredProp;
  }

  get total() {
    return this._sum;
  }
}
For this example we initialize the component like this:
vue
<template>
  <CounterComposablesIvueDestructuring />
</template>
Result
Increment Count: 0
Mouse X: 0, Y: 0
Total (computed): 0

Using Inside Composables

See the highlighted sections related to using ivue inside a composable.

vue
<script setup lang="ts">
import { ivue, iref } from 'ivue';

class Counter {
  count = iref(0);
  increment() {
    this.count++;
  }
}

function useCounter() {
  /** Convert ivue Counter to a composable with .toRefs() */
  const { count, increment } = ivue(Counter).toRefs();

  return {
    count,
    increment,
  };
}

const { count, increment } = useCounter();
</script>
<template>
  <a href="javascript:void(0)" @click="increment">
    Increment
  </a>
  Count: {{ count }} <br />
</template>
For this example we initialize the component like this:
vue
<template>
  <CounterInsideComposables />
</template>
Result
Increment Count: 0

Using Computeds

Getters are computeds in ivue unless disabled.

See the highlighted sections related to getters.

vue
<script setup lang="ts">
import { ivue, iref } from 'ivue';

class Counter {
  count = iref(0);
  increment() {
    this.count++;
  }
  get doubleCount() {
    return this.count * 2;
  }
  get quadCount() {
    return this.doubleCount * 2;
  }
}

const counter = ivue(Counter);
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }} <br />
  Double Count: {{ counter.doubleCount }} <br />
  Quad Count: {{ counter.quadCount }} <br />
</template>
For this example we initialize the component like this:
vue
<template>
  <CounterComputeds />
</template>
Result
Increment Count: 0
Double Count: 0
Quad Count: 0

Disable computed behavior for certain getters in ivue.

You can disable computed getters via static ivue = { getter: false }, see below:

vue
<script setup lang="ts">
import { ivue, iref } from 'ivue';

class Counter {
  /**
   * Use static ivue property to disable computed
   * behavior for certain getters.
   */
  static ivue = {
    doubleCount: false, // doubleCount is a regular getter now
  };
  count = iref(0);
  increment() {
    this.count++;
  }
  get doubleCount() {
    return this.count * 2;
  }
  get quadCount() {
    return this.doubleCount * 2;
  }
}

const counter = ivue(Counter);
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }} <br />
  Double Count: {{ counter.doubleCount }} <br />
  Quad Count: {{ counter.quadCount }} <br />
</template>

The result of this example is identical to the above.

Using Watch

To use watch, watchEffect, and other reactive functions, declare .init() method in the class. See the highlighted sections related to using init() below:

vue
<script setup lang="ts">
import { watch } from 'vue';
import { ivue, iref } from 'ivue';

class Counter {
  init() {
    /** Note that we use this.count as a reactive prop in the watch() function */
    watch(
      () => this.count,
      (newCount) => {
        if (newCount === 5) {
          alert('You already clicked 5 times!');
        }
      }
    );
  }
  count = iref(0);
  increment() {
    this.count++;
  }
}

const counter = ivue(Counter);
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }} (Click 5 times to get an alert)
</template>
For this example we initialize the component like this:
vue
<template>
  <CounterWatch />
</template>
Result
Increment Count: 0 (Click 5 times to get an alert)

Using Lifecycle Hooks

To use onMounted, onBeforeMount and other lifecycle hooks, declare .init() method in the class. See the highlighted sections related to using init() below:

NOTICE:

init() method can be declared as async if needed.

vue
<script setup lang="ts">
import { onMounted } from 'vue';
import { ivue, iref } from 'ivue';

class Counter {
  init() {
    onMounted(() => {
      this.count = 100;
    });
  }
  count = iref(0);
  increment() {
    this.count++;
  }
}

const counter = ivue(Counter);
</script>
<template>
  <a href="javascript:void(0)" @click="() => counter.increment()">
    Increment
  </a>
  Count: {{ counter.count }} (onMounted we set it to 100)
</template>
For this example we initialize the component like this:
vue
<template>
  <CounterLifecycleHooks />
</template>
Result
Increment Count: 0 (onMounted we set it to 100)