Rick
Rick

Reputation: 1274

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

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>

Upvotes: 57

Views: 87495

Answers (12)

ADM-IT
ADM-IT

Reputation: 4182

You can use InstanceType in order to achieve the strong type approach. Please see the example bellow:

UsersPage.vue:

<template>
    <h-table ref="usersTable"></h-table>
</template>

<script lang="ts">
import { defineComponent } from "vue"
import HTable from "@/components/HTable.vue"

type HTableType = InstanceType<typeof HTable>;

export default defineComponent({
    name: "UsersPage",
    components: {
        HTable,
    },
    mounted() {
        (this.$refs.usersTable as HTableType).load(); // "load()" is a strongly typed method which belongs to HTable component. Any modification of that method would cause a compilation error in all places where it's used.
    },
});
</script>

Upvotes: 2

Andy Brown
Andy Brown

Reputation: 5522

I spent a LONG time trying to find an answer to this using Vue 3, TypeScript with class components and (as it happens, although not relevant to this) TipTap. Found the answer from bestRenekton above which finally solved it, but it needed tweaking. I'm pretty sure this is TypeScript specific.

My child component has this at the start:

export default class WhealEditor extends Vue {

It includes this method (the one I want to call from the parent):

doThis(what: string) {
    console.log('Called with ' + what)
  }

And this right at the end:

export type EditorRef = InstanceType<typeof WhealEditor>

</script>

So this announces to any consumer of the child component that it can access it using the variable EditorRef. The parent component includes the child component in the template:

 <WhealEditor ref="refEditor" />

The parent component then imports ref, and the child component and the exposed object:

import { ref } from 'vue'
import WhealEditor, { EditorRef } from './components/WhealEditor.vue'

I then have a method to get this object:

getEditor(): EditorRef {
    // gets a reference to the child component
    return this.$refs.refEditor as EditorRef
  }

Finally, I can handle events - for example:

processButton(msg: string) {
    // runs method in child component
    this.getEditor().doThis(msg)

Like everything else to do with client script, it's so much harder than I expected!

Upvotes: 3

rodrigocfd
rodrigocfd

Reputation: 8023

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).

Upvotes: 2

Jossef Harush Kadouri
Jossef Harush Kadouri

Reputation: 34207

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;

...

Upvotes: 2

John Snow
John Snow

Reputation: 2018

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>
  }
}

Upvotes: 49

bestRenekton
bestRenekton

Reputation: 221

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();

Upvotes: 21

George
George

Reputation: 1974

You can do this:

class YourComponent extends Vue {
  $refs!: {
    checkboxElement: HTMLFormElement
  }

  someMethod () {
    this.$refs.checkboxElement.checked
  }
}

From this issue: https://github.com/vuejs/vue-class-component/issues/94

Upvotes: 53

Anonymous Creator
Anonymous Creator

Reputation: 3789

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
    }
}

Upvotes: 4

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;

Upvotes: 4

user3377090
user3377090

Reputation: 141

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

Upvotes: 12

DrSensor
DrSensor

Reputation: 496

Avoid using bracket < > to typecast because it will conflict with JSX.

Try this instead

update() {
    const plateElement = this.$refs.plate as HTMLInputElement
    this.$emit('input', { plate: plateElement.value });
}

as a note that I always keep remembering

Typescript is just Javascript with strong typing capability to ensure type safety. So (usually) it doesn't predict the type of X (var, param, etc) neither automatically typecasted any operation.

Also, another purpose of the typescript is to make JS code became clearer/readable, so always define the type whenever is possible.

Upvotes: 6

Rick
Rick

Reputation: 1274

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,
    });
}

Upvotes: 0

Related Questions