We use cookies to enhance your experience on the site
CodeWorlds

Advanced Composable Patterns

Welcome to the NOVA LAB Science Laboratory! You already know the basics of composables - now it's time for advanced design patterns. Just as NOVA LAB engineers design modular space station systems, we will design composables ready for any challenge.

Factory Composables

A factory composable is a function that creates composables with different configurations - like a station module factory where each module has the same interfaces but different parameters.

1import { ref, computed } from 'vue'
2
3// Factory: creates a composable for any resource
4function createResourceManager(resourceName, maxCapacity = 100) {
5  const currentLevel = ref(maxCapacity)
6  const isLow = computed(() => currentLevel.value < maxCapacity * 0.2)
7  const isCritical = computed(() => currentLevel.value < maxCapacity * 0.05)
8  const percentage = computed(() =>
9    Math.round((currentLevel.value / maxCapacity) * 100)
10  )
11
12  function consume(amount) {
13    currentLevel.value = Math.max(0, currentLevel.value - amount)
14  }
15
16  function refill(amount) {
17    currentLevel.value = Math.min(maxCapacity, currentLevel.value + amount)
18  }
19
20  return {
21    resourceName,
22    currentLevel,
23    maxCapacity,
24    percentage,
25    isLow,
26    isCritical,
27    consume,
28    refill
29  }
30}
31
32// Usage - each resource has the same methods but independent state
33const oxygen = createResourceManager('Oxygen', 500)
34const fuel = createResourceManager('Fuel', 1000)
35const water = createResourceManager('Water', 200)
36
37oxygen.consume(50) // does not affect fuel or water

Composable Composition

Composables can use other composables - like connecting station modules into larger systems.

1import { ref, computed, watch } from 'vue'
2
3// Base composable
4function useNotifications() {
5  const notifications = ref([])
6
7  function addNotification(message, type = 'info') {
8    notifications.value.unshift({
9      id: Date.now(),
10      message,
11      type,
12      timestamp: new Date()
13    })
14    // Limit to 50 notifications
15    if (notifications.value.length > 50) {
16      notifications.value.pop()
17    }
18  }
19
20  function clearAll() {
21    notifications.value = []
22  }
23
24  return { notifications, addNotification, clearAll }
25}
26
27// Composable that uses another composable
28function useLabMonitor(thresholds) {
29  const temperature = ref(22)
30  const pressure = ref(101)
31  const { notifications, addNotification, clearAll } = useNotifications()
32
33  watch(temperature, (val) => {
34    if (val > thresholds.maxTemp) {
35      addNotification(
36        `Critical temperature: ${val}°C!`,
37        'danger'
38      )
39    }
40  })
41
42  watch(pressure, (val) => {
43    if (val < thresholds.minPressure) {
44      addNotification(
45        `Pressure below normal: ${val} kPa`,
46        'warning'
47      )
48    }
49  })
50
51  return {
52    temperature,
53    pressure,
54    notifications,
55    addNotification,
56    clearAll
57  }
58}

Dependency Injection in Composables

Vue 3 allows dependency injection through

provide/inject
- like central station systems accessible to every module.

1import { ref, provide, inject } from 'vue'
2
3// Symbol as key - avoids name collisions
4const MissionContextKey = Symbol('MissionContext')
5
6// Composable providing context
7function provideMissionContext() {
8  const missionName = ref('Mars Exploration 2087')
9  const crewCount = ref(6)
10  const missionDay = ref(1)
11
12  const context = {
13    missionName,
14    crewCount,
15    missionDay,
16    advanceDay() { missionDay.value++ }
17  }
18
19  provide(MissionContextKey, context)
20  return context
21}
22
23// Composable consuming context
24function useMissionContext() {
25  const context = inject(MissionContextKey)
26  if (!context) {
27    throw new Error(
28      'useMissionContext requires provideMissionContext in a parent component'
29    )
30  }
31  return context
32}
1<!-- ParentComponent.vue -->
2<script setup>
3const { missionName, missionDay, advanceDay } = provideMissionContext()
4</script>
5
6<!-- ChildComponent.vue (any nesting depth) -->
7<script setup>
8const { missionName, missionDay } = useMissionContext()
9// Automatically reactive - changes in parent update children
10</script>

Error Handling in Composables

Every station system must have safeguards in case of failure.

1import { ref, computed } from 'vue'
2
3function useAsyncOperation(asyncFn) {
4  const data = ref(null)
5  const error = ref(null)
6  const loading = ref(false)
7  const retryCount = ref(0)
8  const MAX_RETRIES = 3
9
10  const hasError = computed(() => error.value !== null)
11  const canRetry = computed(() => retryCount.value < MAX_RETRIES)
12
13  async function execute(...args) {
14    loading.value = true
15    error.value = null
16
17    try {
18      data.value = await asyncFn(...args)
19      retryCount.value = 0 // Reset on success
20    } catch (e) {
21      error.value = e.message || 'Unknown error'
22      console.error(`Operation failed: ${error.value}`)
23    } finally {
24      loading.value = false
25    }
26  }
27
28  async function retry(...args) {
29    if (!canRetry.value) {
30      error.value = 'Retry limit exceeded'
31      return
32    }
33    retryCount.value++
34    await execute(...args)
35  }
36
37  return { data, error, loading, hasError, canRetry, retryCount, execute, retry }
38}
39
40// Usage
41const sensorData = useAsyncOperation(async (sensorId) => {
42  const response = await fetch(`/api/sensors/${sensorId}`)
43  if (!response.ok) throw new Error('Failed to fetch data')
44  return response.json()
45})
46
47// In a component
48await sensorData.execute('temp-sensor-1')
49if (sensorData.hasError.value && sensorData.canRetry.value) {
50  await sensorData.retry('temp-sensor-1')
51}
Go to CodeWorlds