Choosing between vm. and this. in Vue.js

My understanding of when to use the "this" keyword in vue.js is a bit cloudy. In the example below, whenever I replace "this" with "vm", the code stops functioning.

I have come across instances where "self" was used instead of "this", but as someone who is not well-versed in JavaScript, this only adds to my confusion.

var vm = new Vue({
        el: '#app',
        data: {
            tickets: [],
            top: 100,
            search: '',
            showAdd: false,
         },
        mounted: function () {
            this.$nextTick(function () {
                console.log('mounted');
                this.GetTickets(100);
            })
        },
        methods: {
            GetTickets: function (top) {
                axios.get('/api/Tickets', {
                    params: {
                        Top: top
                    }
                })
                    .then(function (response) {
                        vm.tickets = response.data;
                    })
                    .catch(function (error) {
                        console.log(error);
                    });
            },
            ClearTicket: function () {
                var t = {
                    "ticketSubject": '',
                    "contactName": '',
                    "createdAt": moment()
                }
                vm.ticket = t;
                vm.showAdd = !vm.showAdd;
            },
            AddTicket: function () {
                //vm.tickets.unshift(vm.ticket);
                axios.post('/api/Tickets', vm.ticket)
                    .then(function (response) {
                        console.log(response);
                        vm.GetTickets(100);
                    })
                    .catch(function (error) {
                        console.log(error);
                    });
                vm.showAdd = false;

            }
        },

    })

Answer №1

Usually, when working inside methods, computed properties, or lifecycle handlers in Vue, you will utilize the keyword this to refer back to the component where the method/computed property/handler is linked. The context of this is determined by the current execution of the function.

The issue arises with using this when a new function is defined within the scope of the current function, which occurs when creating a callback for a promise (e.g., axios.post, axios.get). Take a look at this code snippet:

AddTicket: function () {
  // On this line, "this" points to the Vue instance
  // It's safe to use "this" to access any data properties of the Vue
    axios.post('/api/Tickets', ...)
      .then(function (response) {
        // However, "this" HERE does NOT refer to the Vue!!
        // The explanation for this behavior is provided below
      })
}

In the above scenario, the first comment could be replaced with code that utilizes this to access data properties or invoke methods of the Vue (this.tickets). Nonetheless, the second comment is situated within a new function context, and thus, this will not reference the Vue object. This is due to the fact that when declaring a new function in JavaScript using the function() {} syntax, that function possesses its own distinct function context separate from the enclosing function.

There are various ways to tackle this issue in JavaScript. One common approach nowadays is to either employ a closure to capture the correct this or use an arrow function. Consider this revised code:

AddTicket: function () {
    axios.post('/api/Tickets', ...)
      .then((response) => {
        // In this case, "this" ALSO refers to the Vue instance
      })
}

It's worth noting that in this example, the callback is defined using an arrow function (() => {}). Arrow functions do not establish their own function context but instead rely on the context in which they are declared—referred to as having lexical scope.

Another commonly used workaround is employing a closure.

AddTicket: function () {
  const self = this // Saving a reference to the desired "this"
    axios.post('/api/Tickets', ...)
      .then(function(response){
        // Despite the change in context here, and being unable to utilize
        // "this", we can leverage the previously declared reference (self)
        // pointing to the Vue instance appropriately
        self.tickets = response
      })
}

Lastly, one can utilize the bind method to create a function with a specific this. Nevertheless, this practice is less prevalent nowadays with the availability of arrow functions.

AddTicket: function () {
    axios.post('/api/Tickets', ...)
      .then(function(response){
        this.tickets = response
      }.bind(this)) // Include ".bind(this)" at the end of the function here
}

Under most circumstances, it's advisable not to follow the pattern demonstrated in your question where a reference to the Vue instance is stored in the variable vm and utilized within the Vue object itself. Such a practice is considered undesirable.

Furthermore, detailed information on correctly handling this can be found in numerous tutorials spread across the internet and discussions on platforms like StackOverflow.

Finally, here's the modified code from your initial query ensuring proper use of this:

var vm = new Vue({
  el: '#app',
  data: {
    tickets: [],
    top: 100,
    search: '',
    showAdd: false,
    ticket: null
  },
  mounted: function () {
    // Omitting $nextTick since unnecessary
    this.GetTickets(100)
  },
  methods: {
    GetTickets: function (top) {
      axios.get('/api/Tickets', { params: { Top: top }})
        .then(response => this.tickets = response.data)
        .catch(error => console.log(error));
    },
    ClearTicket: function () {
      var t = {
        "ticketSubject": '',
        "contactName": '',
        "createdAt": moment()
      }
      this.ticket = t;
      this.showAdd = !this.showAdd;
    },
    AddTicket: function () {
      axios.post('/api/Tickets', this.ticket)
        .then(() => this.GetTickets(100))
        .catch(error => console.log(error));

      this.showAdd = false;
    }
  },
})

Answer №2

To truly grasp how it all works, stick to these straightforward guidelines:

Within the Vue object, always employ this, and utilize its reference identifier outside of it:

var vm = new Vue({
  // Use "this" inside
  el: '#app',
  data: {
    something: true
  },
  created: function () {
    this.something = false // such as here
  }
})

// Outside, rely on the reference identifier,
// as there is no alternative
vm.something = null

Avoid using the reference name within the referenced object itself. When you are outside of the Vue object, your only option is to use the reference name.

Inside Vue, the content of this might change. It functions as another object automatically generated within each function/object. Thus, abide by this second guideline: prior to delving into nested, secondary-level functions, store this in a reference/variable. Why?

var vm = new Vue({
  el: '#app',
  data: {
    something: true
  },
  created: function () {
    // In the first level function, 
    // you can utilize "this"
    axios.get('/uri').then(function () {
      // Here, you are in a nested, secondary level
      // function. It will have its own "this" object
      this.something = false // no error here but...
      // You did not alter the value of "something" in the data object,
      // instead, you merely created a new property, also named "something,"
      // within this nested "this" object.
    })
  }
})

As previously mentioned, each function has its own this object. Hence, with 'this.something = false,' you are essentially generating a new property 'something' within the 'this' object in the nested, secondary level function rather than modifying 'this' in the first level function. Put simply, within the nested function, you lose reference to the previous this from the first level function, as its content was overwritten during the creation of the nested function. Therefore, if you need to access the this object from the first level function in the nested function, save it under a different name that won't be overwritten:

var vm = new Vue({
  el: '#app',
  data: {
    something: true
  },
  created: function () {
    var anyName = this // Save a reference to this under a different name
    axios.get('/uri').then(function () {
      this.something = false // no error here, but you are still creating a new property
      anyName.something = false // NOW you are truly altering the value of "something" in data
    })
  }
})

You can choose any name to save it under. However, for simplicity's sake, name it self. Avoid using vm, as this could lead to confusion about whether it clashes with var vm = new Vue() or not. It won't, but eliminate the risk of confusion and just opt for self.

Do not experiment with arrow functions, avoid using bind. Just adhere to these straightforward rules. As you gain more experience, you can (and should) incorporate them, but for now, focus on coding without the hassle of debugging :)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Webpage featuring tabs aligned horizontally

Can anyone guide me on setting up horizontal tabs similar to the design in this image: https://i.sstatic.net/ewXO4.png? Below is the code snippet: <label class="formlabel">Title 1:</label> <div id="radio_form"> <label><inpu ...

Utilize modules within the AppModule to promote modularization and maintain separation of concerns in Angular 2

When using templates like those from the ASP.NET Core JavaScript services, typically a single module named AppModule is included. However, in my application, which is divided into two logical areas (AreaA and AreaB), I thought it would be better to use two ...

Encountering an unknown value within the inner array of a JSON response when using Angular

I received the following JSON data from an API: [ { "CinemaId": "Hfsdfsdfs", "FilmCode": "HIWdfsdfsfsf47", "FilmTitle": "BAfsdfAAR", "CinemaName": "CsfsnfsfsAhmedabad", "CinemaCity": "Ahmedabad", "CinemaLicenseName": "BIGdfsfsAh ...

Instructions for adding an onfocus event listener to an input field in order to dynamically change the formatting of associated labels

I'm looking to change the style of my input labels to a more fancy look by implementing the .fancyclass style when using the onfocus event on the input field. I am curious to know how this can be achieved through event listeners in Javascript? ...

Comparing dynamic and static strings within objects using regex in JavaScript

Suppose there is an object with keys as strings containing dynamic values- let obj = { "/prodp/v1/engagemnt/{engID}/doc/{docID}": "roles_1", "/prodp/v1/engagemnt/{engID}/review/{reviewID}": "roles_2" } How can I find the value in the above object for the ...

Dealing with TSLint errors within the node_modules directory in Angular 2

After installing the angular2-material-datepicker via NPM, it is now in my project's node_modules folder. However, I am encountering tslint errors that should not be happening. ERROR in ./~/angular2-material-datepicker/index.ts [1, 15]: ' should ...

Expanding content based on height using React

I have successfully implemented the show more/show less feature. However, my issue is that I would like it to be based on the number of lines or screen height rather than the number of characters. This is because the current setup may look awkward on certa ...

Issue with Greasemonkey script causing 400 error on POST request

I am currently working on a Django application that exposes an API with the following URL configuration: url('^links/', linkhandler), The link handler is a Django piston resource with a POST (Create function) outlined below: def create(self, ...

Exploring the child element of the present DOM node within Angular

In my template, I have a code snippet similar to the one below: <button class="btn" > <audio src="mySourceFile.wav"></audio> </button> When the button is clicked, I want to play the audio object. Ideally, I would like to achieve ...

Looking for the location of a matching brace in a dataset?

As a newbie in the world of coding, I have embarked on learning about NodeJs file system module. Currently, my focus is on handling a large data file stored as a string. The challenge that I am facing is with locating the matching close brace and its pos ...

"JavaScript encountered an Uncaught TypeError: Cannot read property 'style' of undefined

Looking to create a local time clock using HTML, here is the code: <body> <div class="container text-center"> <div class="row"> <div class="col-xs-12 col-sm-6"> & ...

Unable to utilize the useState hook in TypeScript (Error: 'useState' is not recognized)

Can you identify the issue with the following code? I am receiving a warning from TypeScript when using useState import * as React, { useState } from 'react' const useForm = (callback: any | undefined) => { const [inputs, setInputs] = useS ...

Determining object types based on sibling properties in Typescript

Can you define properties of an object based on another property within the same object? How do I determine the types for values based on the value of the type property? type StepKeys = "Test1" | "Test2"; interface objectMap { &qu ...

Navigating a jQuery collection using iteration

My goal is to loop through an array and set values to an element in the following manner: <tool>hammer</tool> var tools = ["screwdriver", "wrench", "saw"]; var i; for (i=0; i < tools.length; ++i){ $("tool").delay(300).fadeOut().delay(100). ...

The surprising outcome of altering the background color of a div during a click event

Currently, I am engrossed in crafting a word guessing game. In this game, I've constructed a visual keyboard. When a user selects a letter on this keyboard, I desire the background color of that specific letter to adjust accordingly - light green if t ...

Using TypeScript to send state through history.push({...})

I recently utilized the history.push method to redirect to a specific URL while passing along some information through the included state. Here's how I implemented it: const history = useHistory() history.push({ pathname: '/someurl/', ...

Enable synchronized scrolling and a fixed/sticky header with splitpanes feature

Sandbox.vue <template> <v-container fluid> <Splitpanes class="default-theme" style="height: 400px" > <Pane v-for="i in 2" :key="i" > & ...

What could be the reason for one function returning undefined from identical API calls while the other functions produce successful results?

As I delved into incorporating the TMDB API into my project, a perplexing issue arose that has left me stumped. Despite utilizing identical code snippets in two separate files and functions, one of them returns undefined while the other functions flawlessl ...

Tips for initializing store data in vuex before the component is rendered

Currently, I am utilizing Vuex to store a tree structure within the state property. This data is retrieved via an ajax call. The issue arises when the page renders before the variable in the state property has been fully populated. For example: // sth. ...

How to update an object in an array within a collection using ExpressJS and MongoDB

I'm having trouble updating an array within a collection. I can't seem to find the object in the array and add new values to it. I've tried a few methods, but it looks like I can't use collection methods on arrays? router.post('/m ...