1

I created a controller plugin to be used global in every component, but I can't make it work with Vue 3 + TypeScript + Composition API I get a TypeScript error

ui/plugins/controllers.ts

import { App } from 'vue'
import { provider, IProvider } from '@/core/presentation/provider'

export default {
  install: (app: App) => {
    const controllers: IProvider = provider()
    app.provide('controllers', controllers)
  }
}

main.ts

import { createApp } from 'vue'
import { controllers } from './ui'

createApp(App)
  .use(controllers)
  .mount('#app')

@/core/presentation/provider/provider.ts

import { UserController } from '../controllers'
import { IProvider } from './provider.types'

export const provider = (): IProvider => ({
  users: new UserController()
})

ui/views/Component.vue

import { onMounted, ref, inject, defineComponent } from 'vue'

export default defineComponent({
  setup() {
    const controllers = inject('controllers')

    const user = ref()
    const getUser = async () => {
      const result = await controllers.users.getById(1)
      if (result) {
        user.value = result.toJson()
      }
    }

    onMounted(getUser)

    return {
      user,
      getUser
    }
  }
})

Here I get a typescript error when I try to use the controller at this line

const result = await controllers.users.getById(1)

Error:

const controllers: unknown
Object is of type 'unknown'.Vetur(2571)

If I set the type with my interface like this I get another typescript error

import { IProvider } from '@/core'
...
const controllers: IProvider  = inject('controllers')

Error:

type 'IProvider | undefined' is not assignable to type 'IProvider'.
Type 'undefined' is not assignable to type 'IProvider'.Vetur(2322)

I can only make it work like this but I think it's weird:

const controllers: IProvider | undefined = inject('controllers')
...
const result = await controllers?.users.getById(1)
2
  • I think you should add undefined here export const provider = (): IProvider | undefined => ({ Commented Feb 5, 2021 at 17:45
  • @BoussadjraBrahim I get the exact same error: type 'IProvider | undefined' is not assignable to type 'IProvider'. Type 'undefined' is not assignable to type 'IProvider'.Vetur(2322) Commented Feb 5, 2021 at 17:52

2 Answers 2

8

I managed to solve my problem with post Type-safe Vue.js Injections

One thing to note is that the inject function produces the resolved type in union with undefined. This is because there is the possibility that the injection is not resolved. It's up to you how you want to handle it.

So to deal with undefined I followed his advice and create an injectStrict function. Here is my final code:

Component.vue

import { IProvider } from '@/core'
import { injectStrict } from '@/ui/utils'
import { onMounted, ref, defineComponent } from 'vue'

export default defineComponent({
  setup() {
    const controllers: IProvider = injectStrict('controllers')
    const user = ref()
    const getUser = async () => {
      const result = await controllers.users.getById(1)
      if (result) {
        user.value = result.toObj()
      }
    }

    onMounted(getUser)

    return {
      user,
      getUser
    }
  }
})

@/utils/injections.ts

import { inject } from 'vue'

function injectStrict<T>(key: string, fallback?: T) {
  const resolved = inject(key, fallback)
  if (!resolved) {
    throw new Error(`Could not resolve ${key}`)
  }

  return resolved
}

export { injectStrict }
Sign up to request clarification or add additional context in comments.

Comments

0

If you are sure that the injection is always resolved, you can use the Non-null assertion operator:

const controllers: IProvider = inject('controllers')! // <- notice the exclamation mark!
const result = await controllers.users.getById(1)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.