Subscribe on changes!

Add type inference to Options API provide / inject or add docs for not supporting it

avatar
Jan 16th 2021

Version

3.0.5

Reproduction link

https://github.com/l0rn/inject-provide-types/tree/options-api

Steps to reproduce

Use the Provide/Inject mechanism using the Options API and Typescript.

Injected properties won't be inferred and will throw type errors complaining the property is not defined on the component. On the main branch of the same repository you will find the same being achieved using composition API, which works fine.

As far as I understand only using Typescript should not be a reason to use the Composition API I think it would be a cool effort trying to add typing support to inject using the Options API. If that's not feasible I think a warning in the documentation would be appropriate.

What is expected?

Best case scenario:

  • Use provide / inject in options API, all types are properly inferred
  • If any additional type declaration is needed, a paragraph on inject / provide in the typescript section of the docs is provided

What is actually happening?

Using provide / inject in my typescript code, wondering why the injected property is not available in typescript:

ERROR in src/components/Marker.vue:23:19
TS2339: Property 'map' does not exist on type 'ComponentPublicInstance<Readonly<{ location: LocationDto; } & {}>, {}, {}, {}, {}, Record<string, any>, Readonly<{ location: LocationDto; } & {}>, {}, false, ComponentOptionsBase<...>>'.
    21 |     marker
    22 |       .setLngLat([this.location.lng, this.location.lat])
  > 23 |       .addTo(this.map.value)
       |                   ^^^
    24 | 
    25 |   }
    26 | })
avatar
Jan 16th 2021

Would have been helpful to keep the repro online, but it seems you delete the branch demonstrating your issue. :/

avatar
Jan 16th 2021

Hm, i just double checked. I clicked the link in a non-logged in private window and it still shows correctly :thinking:

Maybe the permalink to the problematic line helps? https://github.com/l0rn/inject-provide-types/blob/75743eb91230424db07f6dbfb4095a918c890da7/src/components/Marker.vue#L23

avatar
Jan 16th 2021

I think this is a request to get the type when using inject with Options API

defineComponent({
 inject: [ 'myVar'],
 mounted(){
   this.myVar // doesn't exists
 }
})

Ideally we would also be able to get the typing information, it would be cool to use the InjectionKey<T> for that

const myVar: InjectionKey<{a: 1}> = Symbol()

defineComponent({
 inject: [ myVar],
 mounted(){
   this.myVar // it should have type of `{a: 1}`
 }
})
avatar
Apr 14th 2021

Yes so basically I ran into what I believe is the issue that l0rn is trying to raise here.

Given a vue-cli generated Vue 3 project, the typescript compiler will complain about the injected value not being present on the component when using TypeScript in combination with the Options API. Now, I might be missing something here but I have yet to figure out a way to tackle this.

avatar
Apr 16th 2021

Yes so basically I ran into what I believe is the issue that l0rn is trying to raise here.

Given a vue-cli generated Vue 3 project, the typescript compiler will complain about the injected value not being present on the component when using TypeScript in combination with the Options API. Now, I might be missing something here but I have yet to figure out a way to tackle this.

I just ran into this as well. Here's a tweet from @sstephenson showing an example type definition for a similar api in StimulusJS. It doesn't look like he was able to keep the array syntax to make this work.

I'm going to convert my component to the Composition API and see if that fixes that issue.

avatar
May 25th 2021

Im not familar with inject. If it only accepets string or symbol, best we can do is infering this.map as a unkown or any.

avatar
Jul 30th 2021

Yeah this would be really useful, its pretty frustrating to not be able to use inject, options API, and TypeScript together

avatar
Apr 1st 2022

Okay, I ran into the same issue, and ended up using the inject method, inside a setup function in my Options API component. It's goofy, but it does the trick:

<template>
  <h1>{ a! + b! }<h1>
  <h2>combined: { combined }</h2>
</template>

<script lang="ts">
import { defineComponent, inject } from 'vue'

import type { A, B } from './things'

export default defineComponent({
  setup () {
    return {
      a: inject<A>('a'),
      b: inject<B>('b')
    }
  },

  computed: {
    combined () : string {
      // Typescript treats injects as optionals, so I'm explicitly expanding them with `!`
      return this.a! + this.b!
    }
  }
})
</script>
avatar
Apr 1st 2022

@8bitDesigner that's the correct behaviour, for non optional you need to pass a default.

The reason is depending where you are in the tree it might be or not provided by a parent. Even though "you know" that will never happen, it wouldn't be the first time during refactoring or someone else changing the code that might place that component where it shouldn't be.

avatar
Apr 1st 2022

That totally makes sense, and I fully admit that I will break this the minute I refactor my code, if I haven't already.