Vue.js course Β· Module 15: TypeScript & Nuxt
Template Refs and Provide/Inject with Types
In this lesson4
In the advanced NOVA LAB station systems, we sometimes need to directly reference DOM elements or pass data between components without props drilling. TypeScript helps us do this safely.
Template Refs with Types
A template ref allows you to get a reference to a DOM element:
1<script setup lang="ts">
2import { ref, onMounted } from 'vue'
3
4// HTML element type - ref can be null before mounting
5const inputRef = ref<HTMLInputElement | null>(null)
6const canvasRef = ref<HTMLCanvasElement | null>(null)
7
8onMounted(() => {
9 // After mounting, ref is no longer null
10 inputRef.value?.focus()
11
12 const ctx = canvasRef.value?.getContext('2d')
13 if (ctx) {
14 ctx.fillStyle = '#00ff88'
15 ctx.fillRect(0, 0, 100, 100)
16 }
17})
18</script>
19
20<template>
21 <input ref="inputRef" placeholder="Enter command..." />
22 <canvas ref="canvasRef" width="400" height="300" />
23</template>The type HTMLInputElement | null tells TypeScript that the ref can be null (before the component is mounted).
Component References
You can also get a reference to a child component:
1<script setup lang="ts">
2import { ref } from 'vue'
3import SensorPanel from './SensorPanel.vue'
4
5// InstanceType<typeof Component> gives the component instance type
6const sensorRef = ref<InstanceType<typeof SensorPanel> | null>(null)
7
8function refreshSensors() {
9 // Access public methods of the component
10 sensorRef.value?.refresh()
11}
12</script>
13
14<template>
15 <SensorPanel ref="sensorRef" />
16 <button @click="refreshSensors">Refresh Sensors</button>
17</template>InjectionKey and Provide/Inject
provide/inject allows passing data down the component tree. InjectionKey<T> ensures type safety:
1<!-- types.ts -->
2<script lang="ts">
3import type { InjectionKey, Ref } from 'vue'
4
5export interface StationConfig {
6 name: string
7 maxCrew: number
8 sector: string
9}
10
11// Key with type - ensures provide and inject use the same type
12export const StationConfigKey: InjectionKey<StationConfig> = Symbol('StationConfig')
13export const ThemeKey: InjectionKey<Ref<'dark' | 'light'>> = Symbol('Theme')
14</script>1<!-- Provider (parent) -->
2<script setup lang="ts">
3import { provide, ref } from 'vue'
4import { StationConfigKey, ThemeKey } from './types'
5
6const theme = ref<'dark' | 'light'>('dark')
7
8provide(StationConfigKey, {
9 name: 'NOVA LAB',
10 maxCrew: 100,
11 sector: 'Alpha-7'
12})
13
14provide(ThemeKey, theme)
15</script>1<!-- Consumer (child/grandchild) -->
2<script setup lang="ts">
3import { inject } from 'vue'
4import { StationConfigKey, ThemeKey } from './types'
5
6// TypeScript knows the type! StationConfig | undefined
7const config = inject(StationConfigKey)
8
9// With a default value - it's not undefined
10const theme = inject(ThemeKey, ref('dark'))
11
12// We use optional chaining because config can be undefined
13console.log(config?.name) // 'NOVA LAB'
14console.log(theme.value) // 'dark'
15</script>Pattern with Default Value
To avoid undefined, you can use a default value:
1const config = inject(StationConfigKey, {
2 name: 'Unknown',
3 maxCrew: 0,
4 sector: 'N/A'
5})
6// Now config is always StationConfig (never undefined)You can also use a factory function:
1const config = inject(StationConfigKey, () => ({
2 name: 'Default',
3 maxCrew: 50,
4 sector: 'Beta'
5}), true) // third arg = true means it's a factory functionCode for this lesson: App.vue
1<script setup lang="ts">
2import { ref, provide, onMounted, type InjectionKey } from 'vue'
3import InfoPanel from './InfoPanel.vue'
4
5// --- Template Ref ---
6const searchInput = ref<HTMLInputElement | null>(null)
7const searchQuery = ref('')
8
9onMounted(() => {
10 // After mounting - focus the input
11 searchInput.value?.focus()
12})
13
14function clearSearch() {
15 searchQuery.value = ''
16 searchInput.value?.focus()
17}
18
19// --- InjectionKey ---
20interface StationConfig {
21 name: string
22 sector: string
23 year: number
24}
25
26const StationConfigKey: InjectionKey<StationConfig> = Symbol('StationConfig')
27
28// Provide with a type
29provide(StationConfigKey, {
30 name: 'NOVA LAB',
31 sector: 'Alpha-7',
32 year: 2087
33})
34
35// Export the key so the child can use it
36provide('configKey', StationConfigKey)
37</script>
38
39<template>
40 <div class="nova-lab">
41 <h1>Template Refs & Provide/Inject</h1>
42
43 <div class="search-box">
44 <input
45 ref="searchInput"
46 v-model="searchQuery"
47 placeholder="Search station systems..."
48 class="search-input"
49 />
50 <button @click="clearSearch" class="btn">Clear</button>
51 </div>
52
53 <p v-if="searchQuery" class="query-info">
54 Searching: <strong>{{ searchQuery }}</strong>
55 </p>
56
57 <InfoPanel />
58 </div>
59</template>
60
61<style scoped>
62.nova-lab {
63 background: #0a0e27;
64 color: #00ff88;
65 min-height: 100vh;
66 padding: 20px;
67 font-family: monospace;
68}
69h1 { color: #00ff88; margin-bottom: 15px; }
70.search-box { display: flex; gap: 8px; margin-bottom: 10px; }
71.search-input {
72 flex: 1;
73 background: rgba(0,180,216,0.1);
74 border: 2px solid #00b4d8;
75 color: #00ff88;
76 padding: 10px 14px;
77 border-radius: 8px;
78 font-family: monospace;
79 font-size: 14px;
80 outline: none;
81}
82.search-input:focus { border-color: #00ff88; }
83.btn {
84 background: #00b4d8;
85 color: white;
86 border: none;
87 padding: 10px 16px;
88 border-radius: 8px;
89 cursor: pointer;
90}
91.query-info { color: #00b4d8; font-size: 13px; }
92</style>Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. Why do we define a template ref as
ref<HTMLInputElement | null>(null)?2. What does InjectionKey<T> provide in the provide/inject system?
Hands-on tasks in the game
- Vertical ordering
Arrange the steps for working with a typed template ref:
- Code editor
Define an InjectionKey, use provide in the parent and inject in the child.
- Click in order
Arrange the steps for creating typed provide/inject:
- Horizontal ordering
Arrange the syntax for a typed component reference: