Vuejs typescript this.$refs.<refField>.value does not exist

Viewed 69315

While rewriting my VueJs project in typescript, I came across a TypeScript error.

This is a part of the component that has a custom v-model.

An input field in the html has a ref called 'plate' and I want to access the value of that. The @input on that field calls the update method written below.

Typescript is complaining that value does not exist on plate.

@Prop() value: any;

update() {
    this.$emit('input',
        plate: this.$refs.plate.value
    });
}

template:

<template>  
<div>
    <div class="form-group">
        <label for="inputPlate" class="col-sm-2 control-label">Plate</label>

        <div class="col-sm-10">
            <input type="text" class="form-control" id="inputPlate" ref="plate" :value="value.plate" @input="update">
        </div>
    </div>

</div>
</template>
10 Answers

Edit - 2021-03 (Composition API)

Updating this answer because Vue 3 (or the composition API plugin if you're using Vue 2) has some new functions.

<template>
  <div ref="root">This is a root element</div>
</template>

<script lang="ts">
  import { ref, onMounted, defineComponent } from '@vue/composition-api'

  export default defineComponent({
    setup() {
      const root = ref(null)

      onMounted(() => {
        // the DOM element will be assigned to the ref after initial render
        console.log(root.value) // <div>This is a root element</div>
      })

      return {
        root
      }
    }
  })
</script>

Edit - 2020-04:

The vue-property-decorator library provides @Ref which I recommend instead of my original answer.

import { Vue, Component, Ref } from 'vue-property-decorator'

import AnotherComponent from '@/path/to/another-component.vue'

@Component
export default class YourComponent extends Vue {
  @Ref() readonly anotherComponent!: AnotherComponent
  @Ref('aButton') readonly button!: HTMLButtonElement
}

Original Answer

None of the above answers worked for what I was trying to do. Adding the following $refs property wound up fixing it and seemed to restore the expected properties. I found the solution linked on this github post.

class YourComponent extends Vue {
  $refs!: {
    vue: Vue,
    element: HTMLInputElement,
    vues: Vue[],
    elements: HTMLInputElement[]
  }

  someMethod () {
    this.$refs.<element>.<attribute>
  }
}

This worked for me: use (this.$refs.<refField> as any).value or (this.$refs.['refField'] as any).value

son.vue

const Son = Vue.extend({
  components: {},
  props: {},
  methods: {
    help(){}
  }
  ...
})
export type SonRef = InstanceType<typeof Son>;
export default Son;

parent.vue

<son ref="son" />

computed: {
  son(): SonRef {
    return this.$refs.son as SonRef;
  }
}

//use
this.son.help();

Maybe it will be useful to someone. It looks more beautiful and remains type support.

HTML:

<input ref="inputComment" v-model="inputComment">

TS:

const inputValue = ((this.$refs.inputComment as Vue).$el as HTMLInputElement).value;

In case of custom component method call,

we can typecast that component name, so it's easy to refer to that method.

e.g.

(this.$refs.annotator as AnnotatorComponent).saveObjects();

where AnnotatorComponent is class based vue component as below.

@Component
export default class AnnotatorComponent extends Vue {
    public saveObjects() {
        // Custom code
    }
}

With Vue 3 and the Options API, this is what worked for me:

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

export default defineComponent({
  methods: {
    someAction() {
      (this.$refs.foo as HTMLInputElement).value = 'abc';
    },
  },
});
</script>

The autocomplete doesn't bring the foo property from $refs because it's defined in the template, and apparently there's no information inferred from it.

However, once you force the casting of .foo to the HTML element type, everything works from there on, so you can access any element property (like .value, in the example above).

I found a way to make it work but it is ugly in my opinion.

Feel free to give other/better suggestions.

update() {
    this.$emit('input', {
        plate: (<any>this.$refs.plate).value,
    });
}

Make sure to wrap your exports with Vue.extend() if you are converting your existing vue project from js to ts and want to keep the old format.

Before:

enter image description here

<script lang="ts">

export default {
  mounted() {
    let element = this.$refs.graph;

...

After:

enter image description here

<script lang="ts">

import Vue from "vue";

export default Vue.extend({
  mounted() {
    let element = this.$refs.graph;

...
Related