Categories
JavaScript Vue

Introduction to Vue.js Testing

With apps getting more complex than ever, it’s important to test them automatically. We can do this with unit tests, and then we don’t have to test everything by hand.

In this article, we’ll look at how to test Vue.js apps by writing a simple app and testing it.

Getting Started

To get started, we create an app that gets a joke from the Chuck Norris Jokes API.

We start by creating an empty folder, going into it, and running the Vue CLI by running:

npx vue create .

In the wizard, we select Unit Tests, then choose Jest and then proceed.

Now that we have the files generated, we can change some code. We can delete the components folder and replace the code in App.vue with:

<template>  
  <div id="app">  
    <button @click='toggleJoke()'>{{jokeHidden ? 'Show' : 'Hide'}} Joke</button>  
    <p v-if="!jokeHidden">{{data.value.joke}}</p>  
  </div>  
</template><script>  
export default {  
  name: "app",  
  data() {  
    return {  
      jokeHidden: false,  
      data: { value: {} }  
    };  
  },  
  beforeMount() {  
    this.getJoke();  
  },  
  methods: {  
    async getJoke() {  
      const res = await fetch("http://api.icndb.com/jokes/random");  
      this.data = await res.json();  
    }, toggleJoke() {  
      this.jokeHidden = !this.jokeHidden;  
    }  
  }  
};  
</script><style>  
#app {  
  font-family: "Avenir", Helvetica, Arial, sans-serif;  
  -webkit-font-smoothing: antialiased;  
  -moz-osx-font-smoothing: grayscale;  
  text-align: center;  
  color: #2c3e50;  
  margin-top: 60px;  
}  
</style>

The code just gets a joke from the API and then display it. Also, it has a button to show and hide the joke.

Our app looks something like the following:

https://thewebdev.info/wp-content/uploads/2020/04/test.png

Creating the Tests

Now that we have something to test, we can actually write the tests.

In the tests/unit folder, we delete what we have then create app.spec.js in that folder.

Then we open the file we created and add:

import { mount } from @vue/test-utils';  
import App from '@/App.vue'
const mockResponse = {  
  "type": "success",  
  "value": {  
    "id": 178,  
    "joke": "In an act of great philanthropy, Chuck made a generous donation to the American Cancer Society. He donated 6,000 dead bodies for scientific research.",  
    "categories": []  
  }  
}

To import the component that we’ll test, the mount function to let the Vue Test Utils build and render the component for testing, and the mockResponse object that we’ll use to set the mock data.

Then we add the skeleton for our test by writing:

describe('App.vue', () => {  
  beforeEach(() => {  
    jest.clearAllMocks()  
  })  
})

We have the string description for our test suite and a callback which we add out tests to.

Inside the callback, we have the beforeEach hook to clear all the mocks by running jest.clearAllMocks() .

We need this because we’ll mock some of the functions in our component later.

Adding our First Test

Next, we write our first test. This test will simulate getting the data from the API and then displaying the joke on the screen.

It won’t actually get the joke from the server since we want our test to run anywhere and at any time. Getting it from the server won’t let us do that.

The API returns something different every time we call it and also it might not always be available.

With that in mind, we write:

it('renders joke', async () => {  
    const wrapper = mount(App, {  
      methods: {  
        getJoke: jest.fn()  
      }  
    });  
    wrapper.vm.data = mockResponse;  
      expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke)  
  })

in the callback we passed into the describe function after the beforeEach call.

The test above calls mount on our App component to build and render the component and returns a Wrapper object to let us access it.

In the second argument, we pass in the options with the methods property so that we can mock the getJoke method with Jest with jest.fn(). We want to mock it so that our test doesn’t call the API.

Once we have the wrapper then we run:

wrapper.vm.data = mockResponse;

to set the mockResponse data to the data property of our component instance.

Once we did that, we check that we get the joke in our mockResponse rendered by writing:

expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke)

since we put our joke in the p tag in our App component.

The expect method and toMatch are from Jest.

Writing Test that Interacts with UI Elements

Writing a test that does something to UI elements like buttons isn’t that much more work.

To test the button that we added to our app actually shows and hides the joke, we write:

it('toggles joke', () => {  
    const wrapper = mount(App, {  
      methods: {  
        getJoke: jest.fn()  
      }  
    });  
    wrapper.vm.data = mockResponse;  
    expect(wrapper.find('button').text()).toMatch('Hide Joke');  
      expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke); wrapper.find('button').trigger('click');  
    expect(wrapper.find('button').text()).toMatch('Show Joke');  
    expect(wrapper.find('p').exists()).toBe(false); wrapper.find('button').trigger('click');  
    expect(wrapper.find('button').text()).toMatch('Hide Joke');  
      expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke);  
  })

The first part:

const wrapper = mount(App, {  
  methods: {  
    getJoke: jest.fn()  
  }  
});  
wrapper.vm.data = mockResponse;

is the same as before. We mock the getJoke function with jest.fn() so that our test won’t call the API. Then set the mock data.

Next, we check the button text by writing:

expect(wrapper.find('button').text()).toMatch('Hide Joke');

and that our mocked joke is shown in the p element:

expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke);

Then we click our button by running:

wrapper.find('button').trigger('click');

And then check for the text of the button and whether the p element is removed by our v-if directive:

expect(wrapper.find('button').text()).toMatch('Show Joke');  
expect(wrapper.find('p').exists()).toBe(false);

Finally, we can do the click again and check if the joke is shown again as follows:

wrapper.find('button').trigger('click');  
expect(wrapper.find('button').text()).toMatch('Hide Joke');  
expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke);

Running the Tests

Together, we have the following test code in app.test.js :

import { mount } from '@vue/test-utils';  
import App from '@/App.vue'
const mockResponse = {  
  "type": "success",  
  "value": {  
    "id": 178,  
    "joke": "In an act of great philanthropy, Chuck made a generous donation to the American Cancer Society. He donated 6,000 dead bodies for scientific research.",  
    "categories": []  
  }  
}

describe('App.vue', () => {  
  beforeEach(() => {  
    jest.clearAllMocks()  
  }) 

  it('renders joke', async () => {  
    const wrapper = mount(App, {  
      methods: {  
        getJoke: jest.fn()  
      }  
    });  
    wrapper.vm.data = mockResponse;  
      expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke)  
  }) 

  it('toggles joke', () => {  
    const wrapper = mount(App, {  
      methods: {  
        getJoke: jest.fn()  
      }  
    });  
    wrapper.vm.data = mockResponse;  
    expect(wrapper.find('button').text()).toMatch('Hide Joke');  
      expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke); wrapper.find('button').trigger('click');  
    expect(wrapper.find('button').text()).toMatch('Show Joke');  
    expect(wrapper.find('p').exists()).toBe(false); wrapper.find('button').trigger('click');  
    expect(wrapper.find('button').text()).toMatch('Hide Joke');  
    expect(wrapper.find('p').text()).toMatch(mockResponse.value.joke);  
  })  
})

Then we run the tests by npm run test:unit .

We should get:

PASS  tests/unit/app.spec.js  
  App.vue  
    √ renders joke (19ms)  
    √ toggles joke (11ms)Test Suites: 1 passed, 1 total  
Tests:       2 passed, 2 total  
Snapshots:   0 total  
Time:        2.102s  
Ran all test suites.

every time that we run our tests since we mocked the data.

Conclusion

Vue CLI creates a project that has unit testing built-in if we choose to include it. This saves us lots of work.

Jest is an easy test runner with lots of features like mocking and expect matchers that we can use.

To test UI components, we use the wrapper object returned by mount , which has the rendered component. Then we can use find to search the DOM for what we want to look for.

If the element exists, we can also trigger events on it by calling the trigger method with the event that we want to fire.

Finally, we have the exists method to check if the element we look for actually exists.

Categories
JavaScript Vue

Handling Form Input with Vue.js — Input Bindings

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to bind data in various ways and automatically modify inputs.

Value Bindings

v-model bind values usually as static strings, or booleans for a checkbox.

For example, for checkbox, we can bind it to a string as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    selected: undefined  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      foo  
      <input type="radio" v-model="selected" value="foo" />  
      <br />  
      bar  
      <input type="radio" v-model="selected" value="bar" />  
      <br />  
      <span>Selected: {{ selected }}</span>  
    </div> <script src="src/index.js"></script>  
  </body>  
</html>

Checkboxes are bound to a boolean value:

<input type="checkbox" v-model="toggle">

Select are bound to the selected string set in the value attribute:

<select v-model="selected">     
  <option value="foo">Foo</option>   
</select>

Checkbox

We can change the true and false value of a checkbox with the true-value and false-value attributes as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    selected: undefined  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      Toggle  
      <input  
        type="checkbox"  
        v-model="selected"  
        true-value="yes"  
        false-value="no"  
      />  
      <br />  
      <span>Selected: {{ selected }}</span>  
    </div> <script src="src/index.js"></script>  
  </body>  
</html>

Then we get yes when we checked the checkbox and no otherwise.

true-value and false-value don’t affect the input’s value attribute because browsers don’t include unchecked boxes in form submissions.

We should use radio inputs to guarantee at least one item is selected.

Radio Buttons

We can bind the value attribute to the selected value with v-bind:value .

For example, we can write the following:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    pick: "",  
    foo: "foo",  
    bar: "bar"  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      Foo  
      <input type="radio" v-model="pick" v-bind:value="foo" />  
      <br />  
      Bar  
      <input type="radio" v-model="pick" v-bind:value="bar" />  
      <br />  
      <p>Selected: {{ pick }}</p>  
    </div> <script src="src/index.js"></script>  
  </body>  
</html>

Then v-bind:value=”foo” renders as value='foo' and v-bind:value=”bar” is rendered as value='bar' .

When we pick a value, we get foo or bar depending on which radio button is clicked.

Select Options

We can bind a value to an object as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    selected: undefined  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <select v-model="selected">  
        <option v-bind:value="{ number: 123 }">123</option>  
        <option v-bind:value="{ number: 456 }">456</option>  
      </select>  
      <br />  
      <p>Selected: {{ selected }}</p>  
    </div> <script src="src/index.js"></script>  
  </body>  
</html>

Then we get either { number: 123 } or { number: 456 } displayed depending on what’s selected.

If we want to display the value of the number property, then we have to write the following:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    selected: {}  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <select v-model="selected">  
        <option v-bind:value="{ number: 123 }">123</option>  
        <option v-bind:value="{ number: 456 }">456</option>  
      </select>  
      <br />  
      <p>Selected: {{ selected.number }}</p>  
    </div> <script src="src/index.js"></script>  
  </body>  
</html>

Since setting the initial value to undefined will give us an error and the app won’t run.

Modifiers

Vue comes with a few modifiers for v-model .

.lazy

We can use v-model.lazy to sync data on change events rather than input events. This means that changes are only synced when the input it out of focus.

For example, we can use it as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    msg: ""  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <input v-model.lazy="msg" />  
      <br />  
      <p>Value: {{ msg }}</p>  
    </div> <script src="src/index.js"></script>  
  </body>  
</html>

When we move out cursor out of the input, then we get the input value displayed.

.number

The number modifier will convert whatever’s entered into a number. This is useful because input with type='number' still returns a string.

For example, we can use it as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    age: 0  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <input v-model.number="age" type="number" />  
      <br />  
      <p>Age: {{ age }}</p>  
    </div> <script src="src/index.js"></script>  
  </body>  
</html>

.trim

We can trim whitespace automatically with the .trim modifier. To use it, we can write the following:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    msg: ""  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <input v-model.trim="msg" type="text" />  
      <br />  
      <p>Value: "{{ msg }}"</p>  
    </div> <script src="src/index.js"></script>  
  </body>  
</html>

Then when we type in something with starting and ending whitespaces, they won’t show up.

So:

" a b c "

will become "a b c" .

Conclusion

We can bind dynamically to values with the v-bind:value attribute. It lets us bind value attributes to primitive values and objects.

Also, we can use various modifiers on v-model to automatically change the input value in various ways like trimming input and converting them to numbers.

Categories
JavaScript Vue

Making Code Reusable with Vue.js Mixins

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to define and use mixins to make code reusable.

Basics

We can define a mixin by writing some simple code that consists of an object with some properties of components as follows:

src/index.js :

const appMixin = {  
  created() {  
    this.hello();  
  },  
  methods: {  
    hello() {  
      this.message = "hi";  
    }  
  }  
};
new Vue({  
  el: "#app",  
  mixins: [appMixin],  
  data: {  
    message: ""  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      {{message}}  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we defined the mixin appMixin by assigning it to an object with the structure that are the same as the Vue instance.

Therefore, hello gets called when the Vue instance is created since we have the created hook in appMixin .

Then in the template, we display the message , so we get hi on the screen.

Other properties we can put include methods , components , and directives . They’ll all be merged into the object we pass into new Vue or the component options object.

If there’s anything that’s the same, the component’s options will take priority over the items in the mixin.

For example, if we have the following:

src/index.js :

const appMixin = {  
  created() {  
    this.hello();  
  },  
  methods: {  
    hello() {  
      this.message = "hi";  
    }  
  }  
};
new Vue({  
  el: "#app",  
  mixins: [appMixin],  
  data: {  
    message: ""  
  },  
  created() {  
    this.hello();  
  },  
  methods: {  
    hello() {  
      this.message = "foo";  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      {{message}}  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then foo is displayed since the methods in the Vue instance takes precedence over the methods in the mixin.

We can also use the Vue.extend method to create a new component as follows:

src/index.js :

const appMixin = {  
  created() {  
    this.hello();  
  },  
  methods: {  
    hello() {  
      this.message = "hi";  
    }  
  }  
};
const Component = Vue.extend({  
  mixins: [appMixin],  
  el: "#app"  
});

new Component();

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      {{message}}  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Vue.extend works the same way as new Vue that we have above.

The same merging mechanism is used with Vue.extend() .

Option Merging

We can adjust how items are merged by selecting the appropriate strategy for merging.

For example, data objects are merged recursively, with component’s data taking priority in case of conflicts.

src/index.js :

const mixin = {  
  data() {  
    return {  
      message: "hi",  
      foo: "abc"  
    };  
  }  
};

new Vue({  
  el: "#app",  
  mixins: [mixin],  
  data() {  
    return {  
      message: "bye",  
      bar: "def"  
    };  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      {{message}} {{foo}} {{bar}}  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

We see bye abc def displayed since message is in both the component and the mixin, and the component takes precedence.

foo and bar are only in the mixin and component respectively, so they’re both merged into the same data object.

Hooks with the same name are merged into an array so that they all will be called. Mixin hooks will be called the component’s own hooks.

So if we have:

src/index.js :

const mixin = {  
  mounted() {  
    console.log("mixin hook called");  
  }  
};

new Vue({  
  el: "#app",  
  mixins: [mixin],  
  mounted() {  
    console.log("component hook called");  
  }  
});

We see:

mixin hook called  
component hook called

in the console.log output.

Options that expect object values like methods , components , and directives are merged into the same object. The component’s options will take priority when there’re conflicts in the objects.

For example, if we have:

src/index.js :

const mixin = {  
  methods: {  
    foo() {  
      console.log("foo");  
    }  
  }  
};

const vm = new Vue({  
  el: "#app",  
  mixins: [mixin],  
  methods: {  
    foo() {  
      console.log("bar");  
    }  
  }  
});  
vm.foo();

We’ll see bar logged since the component’s foo method takes precedence over the mixin ‘s foo method.

The same merging strategies are used in Vue.extend().

Conclusion

Mixins allow us to create code that can be included in multiple components.

They’re merged by taking component’s methods , components , and directives taking precedence over the mixin’s items in these properties.

Data are merged together as well, with component’s data taking precedence if the same key exists in both places.

Hooks are merged into an array, with the mixin’s hook being called before the component’s hook if they have the same name.

Categories
JavaScript Vue

Vue.js Templates: Modifiers and Shorthands

Vue.js is an easy to use web app framework that we can use to develop interactive front-end apps.

In this article, we’ll look at modifiers for directives and some useful template shorthands.


Modifiers

Modifiers are used for binding a directive in a special way.

Event modifiers

For example, the .prevent modifier for the v-on directive will automatically run event.preventDefault on the event handler function that’s set as the value.

The .prevent modifier can be used as follows:

<form v-on:submit.prevent="onSubmit"> ... </form>

Then, event.preventDefault() will be run when the onSubmit handler is run, before the rest of the code for onSubmit is run.

Other event modifiers include:

  • .stop
  • .capture
  • .self
  • .once
  • .passive

.stop runs event.stopPropagation() before the rest of the event handler code is run.

.capture lets us capture the event. That is, when we run the event handler in an inner element, then the same event handler will also run in the outside elements.

For example, if we have the following in src/index.js:

And the following in index.html:

Then, when we click Click Me, we’ll see the “clicked” alert box since the onClick handler is called on the parent div element.

.self will only trigger the event handler if the event.target is the element itself.

.once will trigger an event handler at most once.

For example, if we have the following in src/index.js:

And the following in index.html:

Then we only see the “clicked” alert box once even though we click “Click me” multiple times.

.passive will set the addEventListener’s passive option to true. passive set to true means that preventDefault() will never be called.

It’s used for improving performance on mobile devices.

Model modifiers

v-model has modifiers. They’re:

  • .lazy
  • .number
  • .trim

The .lazy modifier will make the model sync after the change event instead of the input event.

For example, when we have src/index.js:

new Vue({  
  el: "#app",  
  data: { msg: "" }  
});

And write the following in index.html:

Then we only see msg rendered when we move focus away from the input.

The .number modifier will automatically cast whatever is inputted in an input to a number.

For instance, if we have the following in src/index.js:

new Vue({  
  el: "#app",  
  data: { num: 0 }  
});

And the following in index.html:

Then we’ll get a number for num.

The .trim modifier will trim whitespace from whatever is inputted.

We can use it as follows:

<input v-model.trim="msg" type="number" />

Shorthands

There are shorthands for some directives.

v-bind

We can shorten v-bind: to :. For example, we can rewrite:

<a v-bind:href="url">Link</a>

To:

<a :href="url">Link</a>

We can also put in a dynamic argument if we use Vue 2.6.0 or later:

<a :[key]="url">Link</a>

v-on

We can shorten v-on: to @.

For example, we can shorten:

<a v-on:click="onClick">Click me</a>

To:

<a @click="onClick">Click me</a>

Since Vue 2.6.0, we can also use dynamic arguments as follows:

<a @[event]="onClick">Click me</a>

: and @ are valid characters. Also, they won’t appear in the final markup. Shorthands are totally optional. But we’ll appreciate them when we have to type them in a lot.


Conclusion

Some directives have modifiers that are associated with them.

The v-on directive has multiple modifiers for controlling how event handlers are called.

Also, the v-model directive has some modifiers to let us convert input to numbers automatically or trim whitespace from inputs.

v-on and v-bind also have shorthands. v-on: can be shortened to @, and v-bind: can be shortened to :.

Directive arguments also work with shorthands.

Categories
JavaScript Vue

List Rendering with Vue.js — Object Change Detection and Arrays

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at Vue’s object change detection capabilities and displaying sorted and filtered results.

Object Change Detection

Vue can’t detect property additions or deletions because of the limitations of modern JavaScript.

For example, if we have the following code:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    foo: {  
      a: 1  
    }  
  },  
  methods: {  
    addProperty() {  
      this.foo.b = 2;  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <button @click="addProperty">Add Property</button>  
      <div v-for="value in foo">  
        {{value}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then nothing happens when we click Add Property.

We can fix this by calling Vue.set as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    foo: {  
      a: 1  
    }  
  },  
  methods: {  
    addProperty() {  
      Vue.set(this.foo, "b", 2);  
    }  
  }  
});

and keep index.html the same, we’ll get ‘2’ when we click Add Property.

In the code above, we passed in the object to update as the first argument, the property name as the second argument, and the value of the property as the third argument.

We can also use the this.$set / vm.$set method as follows, where this and vm are a Vue instance.

To use it, we can write the following:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    foo: {  
      a: 1  
    }  
  },  
  methods: {  
    addProperty() {  
      this.$set(this.foo, "b", 2);  
    }  
  }  
});

Then we get the same result as before.

To assign multiple new properties to an existing object, we can use Object.assign or Lodash’s _.extend method by assigning the returned object to the existing variable as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    foo: {  
      a: 1  
    }  
  },  
  methods: {  
    addProperties() {  
      this.foo = Object.assign({}, this.foo, {  
        b: 2,  
        c: 3  
      });  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <button @click="addProperties">Add Properties</button>  
      <div v-for="value in foo">  
        {{value}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we used the Object.assign method with the empty object as the first argument, the original object as the second argument, and an object with the new properties and values as the third argument.

Then when the page first loads, we have only:

1

listed.

Then when we click Add Properties, we get:

123

This is because clicking Add Properties triggered a call to addProperties , which then calls Object.assign to return a merged object with the new properties, which is then assigned back to this.foo .

That will trigger a view update with the new items.

Displaying Filtered/Sorted Results

We should use computed properties to render filtered or sorted results.

This is a much more efficient way to rendered filtered results than checking each array entry during v-for with v-if since the whole array doesn’t have to rendered and then checked with v-if .

For example, we can render a list of odd numbers from a list of all kinds of numbers as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    numbers: [1, 2, 3, 4, 5]  
  },  
  computed: {  
    oddNumbers() {  
      return this.numbers.filter(n => n % 2 === 1);  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="num in oddNumbers">  
        {{num}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get:

135

displayed.

We can use methods in situations where computed properties aren’t feasible like inside nestedv-for loops.

For example, we can write a method as follows to render a list of odd numbers:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    numbers: [1, 2, 3, 4, 5]  
  },  
  methods: {  
    oddNumbers(numbers) {  
      return numbers.filter(n => n % 2 === 1);  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="num in oddNumbers(numbers)">  
        {{num}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we called the oddNumbers method with the numbers array passed in to return an array of odd numbers.

Then we can iterate through it by using the v-for loop as usual.

We can display sorted numbers as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    numbers: [1, 2, 3, 4, 5]  
  },  
  computed: {  
    reversedNumbers() {  
      return this.numbers.sort((a, b) => b - a);  
    }  
  }  
});

index.js :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="num in reversedNumbers">  
        {{num}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

We sorted it with the computed property reversedNumbers to sort the numbers in reverse and then the numbers will be displayed in reverse:

54321

Conclusion

Vue can’t detect object property changes and trigger and view update as properties are added or removed.

Therefore, we have to use Vue.set or this.$set to update it. We can pass in the object to update as the first argument, the property name as the second argument, and the value of the property as the third argument.

To add multiple object properties at once, we can use the Object.assign method with the empty object as the first argument, the original object as the second argument, and an object with the new properties and values as the third argument.

To display sorted and filtered array results, we can use computed properties. It’s the most efficient way since only the filtered results are rendered.

If that can’t be done, then we can also use a method to return the filtered or sorted array instead.