Categories
JavaScript Testing

Running Repetitive Test Code with Jest

In our unit tests, we often have to write test code that runs before and after each test. We’ve to write them often to run code to set up fixtures before tests and run code to clean up everything after tests.

We can easily do this with Jest since it comes with a few hooks to do this.

In this article, we’ll look at how to write repetitive setup and teardown code in a way that isn’t repetitive.

How to Write Setup and Teardown Code

With Jest, we can write setup and teardown code by using the beforeEach and afterEach hooks.

beforeEach runs code before each test, and afterEach runs code after each test. The order applies inside a describe block and if there’s no describe block, for the whole file.

For example, given that we have the following code in example.js :

const getStorage = (key) => localStorage.getItem(key);  
const setStorage = (key, value) => localStorage.setItem(key, value);  
module.exports = {  
    getStorage,  
    setStorage  
}

We write the test for them as follows:

const { getStorage, setStorage } = require('./example');

beforeEach(() => {  
    localStorage.setItem('foo', 1);  
    expect(localStorage.getItem('bar')).toBeNull();  
});

afterEach(() => {  
    localStorage.clear();  
});

test('getStorage("foo") is 1', () => {  
    expect(getStorage('foo')).toBe('1');  
});

test('setStorage saves data to local storage', () => {  
    setStorage('bar', 2);  
    const bar = +localStorage.getItem('bar');  
    expect(getStorage('foo')).toBe('1');  
    expect(bar).toBe(2);  
});

We pass a callback function into the beforeEach hook to run code before each test that we have below.

In this example, we prepopulate local storage with an item with key 'foo' and value '1' .

We also check that local storage doesn’t have the item with key 'bar’ with:

expect(localStorage.getItem('bar')).toBeNull();

All tests will pass with the expect we have above since we’ll run localStorage.clear() in the afterEach hook.

Likewise, we pass in a callback to afterEach to run code after each test is run.

We clear local storage with:

localStorage.clear();

Then when running the tests, we get that item with key'bar' is null since we didn’t populate it in the first test.

In the second test, we’ll get that expect(getStorage(‘foo’)).toBe(‘1’); passing since we populated the local storage with it in our beforeEach hook.

Then since we ran:

setStorage('bar', 2);

to to save an item with key 'bar' and value '2' , we’ll get that:

expect(bar).toBe(2);

passing since we saved the item in the test.

Asynchronous Code

In the example above, we ran synchronous code in our hooks. We can also run asynchronous code in our hooks if they either take a done parameter or return a promise.

If we want to run a function that takes a callback and runs it asynchronously as follows:

const asyncFn = (callback) => {  
    setTimeout(callback, 500);  
}

Then in the beforeEach hook we can run asyncFn as follows:

const { asyncFn } = require('./example');

beforeEach((done) => {  
    const callback = () => {  
        localStorage.setItem('foo', 1);  
        done();  
    }  
    asyncFn(callback);  
    expect(localStorage.getItem('bar')).toBeNull();  
});

It does the same thing as the previous beforeEach callback except it’s done asynchronously. Note that we call done passed in from the parameter in our callback.

If we omit, it, the tests will fail as they time out. The code in the tests is the same as before.

We can wait for promises to resolve by returning it in the callback we pass into the hooks.

For example, in example.js , we can write the following function to run a promise:

const promiseFn = () => {  
    return new Promise((resolve) => {  
        localStorage.setItem('foo', 1);  
        resolve();  
    });  
}

Then we can put promiseFn in module.exports and then run it in our beforeEach by running:

beforeEach(() => {  
    expect(localStorage.getItem('bar')).toBeNull();  
    return promiseFn();  
});

We ran promiseFn which we imported before this hook and we return the promise returned by that function, which sets the local storage like the first example except it’s done asynchronously.

Then after we put everything together, we have the following code in example.js , which we run in our test code in the hooks and for testing:

const getStorage = (key) => localStorage.getItem(key);  
const setStorage = (key, value) => localStorage.setItem(key, value);  
const asyncFn = (callback) => {  
    setTimeout(callback, 500);  
}  
const promiseFn = () => {  
    return new Promise((resolve) => {  
        localStorage.setItem('foo', 1);  
        resolve();  
    });  
}  
module.exports = {  
    getStorage,  
    setStorage,  
    asyncFn,  
    promiseFn  
}

Then we have asyncExample.test.js to change the hook to be asynchronous:

const { getStorage, setStorage, asyncFn } = require('./example');

beforeEach((done) => {  
    const callback = () => {  
        localStorage.setItem('foo', 1);  
        done();  
    }  
    asyncFn(callback);  
    expect(localStorage.getItem('bar')).toBeNull();  
});

afterEach(() => {  
    localStorage.clear();  
});

test('getStorage("foo") is 1', () => {  
    expect(getStorage('foo')).toBe('1');  
});

test('setStorage saves data to local storage', () => {  
    setStorage('bar', 2);  
    const bar = +localStorage.getItem('bar');  
    expect(getStorage('foo')).toBe('1');  
    expect(bar).toBe(2);  
});

Then in example.test.js we have:

const { getStorage, setStorage } = require('./example');

beforeEach(() => {  
    localStorage.setItem('foo', 1);  
    expect(localStorage.getItem('bar')).toBeNull();  
});

afterEach(() => {  
    localStorage.clear();  
});

test('getStorage("foo") is 1', () => {  
    expect(getStorage('foo')).toBe('1');  
});

test('setStorage saves data to local storage', () => {  
    setStorage('bar', 2);  
    const bar = +localStorage.getItem('bar');  
    expect(getStorage('foo')).toBe('1');  
    expect(bar).toBe(2);  
});

and finally in promiseExample.test.js we have:

const { getStorage, setStorage, promiseFn } = require('./example');

beforeEach(() => {  
    expect(localStorage.getItem('bar')).toBeNull();  
    return promiseFn();  
});

afterEach(() => {  
    localStorage.clear();  
});

test('getStorage("foo") is 1', () => {  
    expect(getStorage('foo')).toBe('1');  
});

test('setStorage saves data to local storage', () => {  
    setStorage('bar', 2);  
    const bar = +localStorage.getItem('bar');  
    expect(getStorage('foo')).toBe('1');  
    expect(bar).toBe(2);  
});

BeforeAll and AfterAll

To only run the setup and teardown code once in each file, we can use the beforeAll and afterAll hooks. We can pass in a callback with the code that we want to run like we did with the beforeEach and afterEach hooks.

The only difference is that the callbacks are run once before the test in a file is run and after the test in a file is run instead of running them before and after each test.

Callback of beforeAll runs after the callback for beforeEach and callback for afterAll runs after the callback forafterEach .

The order applies inside a describe block and if there’s no describe block, for the whole file.

Running Only One Test

We can write test.only instead of test to run only one test. This is handy for troubleshooting since we don’t have to run all the tests and it helps us pin issues with the test that’s failing.

To write test code that needs to be run for all tests, we use the beforeEach and afterEach hooks in Jest.

To write test code that’s only run per describe block or file, we can use the beforeAll and afterAll hooks.

Callback of beforeAll runs after the callback for beforeEach and callback for afterAll runs after the callback forafterEach .

Categories
Express JavaScript

Guide to the Express Response Object — Redirection and Templates

The Express response object lets us send a response to the client.

Various kinds of responses like strings, JSON, and files can be sent. Also, we can send various headers and status code to the client in addition to the body.

In this article, we’ll look at various properties of the response object, including sending the Links response header, redirect to different paths and URLs, and rendering HTML.

Methods

res.links(links)

The links method joins an object with links together and sends the Link response header with the links joined together.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {  
  res  
    .links({  
      next: 'http://foo.com?page=1',  
      last: 'http://foo.com?page=2'
    })  
    .send();  
});
app.listen(3000, () => console.log('server started'));

Then we get that the Link response header has the value:

<http://foo.com?page=1>; rel="next", <http://foo.com?page=2>; rel="last"

when we make a request to the / path.

res.location(path)

The location method sends the Location response header with the specified path parameter as the value.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {  
  res  
    .location('http://medium.com')  
    .send();  
});
app.listen(3000, () => console.log('server started'));

Then we get:

http://medium.com

as the value of the Location response header when we make a request to the / route.

res.redirect([status,] path)

The redirect method redirects to the URL or path specified with the specified status . If no status is specified, the default is 302.

For example, we can use it as follows to redirect to another route:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {  
  res.redirect('/bar');  
});

app.get('/bar', (req, res) => {  
  res.send('bar');  
});

app.listen(3000, () => console.log('server started'));

Then we should see bar since we redirected to the bar route.

We can specify a status code as follows:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {  
  res.redirect(301, '/bar');  
});

app.get('/bar', (req, res) => {  
  res.send('bar');  
});

app.listen(3000, () => console.log('server started'));

We can also redirect to a full URL:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {  
    res.redirect('http://google.com');  
});

app.listen(3000, () => console.log('server started'));

Then when we go to / in our browser, we get Google displayed in our browser.

We can redirect back to the referrer with:

res.redirect('back')

and we can also redirect to a higher level URL, which defaults to / .

We can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();  
const post = express.Router();app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));

post.get('/', (req, res) => {  
    res.redirect('..');  
});

app.get('/', (req, res) => {  
    res.send('hi');  
});

app.use('/post', post);app.listen(3000, () => console.log('server started'));

Then we get hi if we go to /post since we see redirect to '..' .

res.render(view [, locals] [, callback])

res.render lets us render HTML using a template. Once we set the HTML template engine, we can use template files to display the results.

The locals object has the properties that we can access in the templates.

And the callback can get the error and the rendered HTML string.

The view argument has the string with the template’s file name. The view’s folder is set before we can call res.render so we don’t need the full folder name.

For example, we can use the Pug template to render templates as follows:

const express = require('express')  
const app = express()  
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))  
app.set('views', './views');  
app.set('view engine', 'pug');
app.get('/', (req, res, next) => {  
  res.render('index', { pageTitle: 'Hello', pageMessage: 'Hello there!' });  
})  
app.listen(3000, () => console.log('server started'));

Then we can add a template file called index.pug to the views folder as follows:

html  
  head  
    title= pageTitle  
  body  
    h1= pageMessage

Then we get:

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

We can also pass in a callback to the render method as follows:

const express = require('express')  
const app = express()  
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))  
app.set('views', './views');  
app.set('view engine', 'pug');app.get('/', (req, res, next) => {  
  res.render(  
    'index',  
    { pageTitle: 'Hello', pageMessage: 'Hello there!' },  
    (err, html) => {  
      if (err) {  
        next(err);  
      }  
      else {  
        console.log(html);  
      }  
    }  
  );  
})  
app.listen(3000);

Then we get the rendered HTML from the console.log :

<html><head><title>Hello</title></head><body><h1>Hello there!</h1></body></html>

Any error will be sent to the Express error handler with the next function.

Conclusion

We can render HTML results with render method.

With the redirect method, we can redirect to different paths and URLs with the different response code.

We can use the links and locations methods to send the Links and Locations response headers respectively.

Categories
JavaScript JavaScript Basics

More Useful DOM Traversal Methods

The main use of client-side JavaScript is to manipulate web pages dynamically. We can do this with the DOM traversal methods and properties available to DOM Node objects.

Adding or changing child and sibling elements is easy for any given element since there are properties built into DOM node objects to do so. The following are methods of a DOM node object for getting parent, child and sibling nodes or elements.

Below are more useful DOM traversal methods.

hasChildNodes

The hasChildNodes method returns a boolean that indicates whether the node it’s called on has any child nodes. Nodes include all kinds of nodes like element, text and comment nodes

For example, given the following HTML:

<div></div>

Then we can call hasChildNodes on it as follows:

const div = document.querySelector('div');  
console.log(div.hasChildNodes())

We should get false since the div has no child nodes.

On the other hand, if we have:

<div>  
  foo  
</div>

Then hasChildNodes returns true since there’s a text node inside the div.

insertBefore

The insertBefore method lets us add a node before the node that this method called on. It takes 2 arguments. The first is the new node to be inserted. The second is the node before the new node that it’s inserted, which is called the reference node.

If the reference node is null , then it’ll be inserted at the end of the list of child nodes.

It returns the add child except when the new node is a DocumentFragement . In that case, only DocumentFragement is returned.

For example, given the following HTML:

<div id='foo'>  
  foo  
  <div id='bar'>  
    bar  
  </div>  
</div>

We can add a new div element after the div with ID bar inside the one with ID foo by writing:

const foo = document.querySelector('#foo');
const newDiv = document.createElement('div');  
newDiv.textContent = 'new';
foo.insertBefore(newDiv, null);

The HTML structure looks like:

<div id="foo">  
  foo  
  <div id="bar">  
    bar  
  </div>  
  <div>new</div>  
</div>

after insertBefore is called.

If we pass in the second argument, then the element in the first argument will be inserted before the second one.

For example, given the following HTML:

<div id='foo'>  
  foo  
  <div id='bar'>  
    bar  
  </div>  
</div>

Then we can insert an element before the one with ID bar at the same level by writing:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');
const newDiv = document.createElement('div');  
newDiv.textContent = 'new';
foo.insertBefore(newDiv, bar);

Then we get the following HTML after insertBefore is called:

<div id="foo">  
  foo  
  <div>new</div>  
  <div id="bar">  
    bar  
  </div>  
</div>

isEqualNode

The isEqualNode method checks whether 2 nodes are equal. They’re equal when they have the same type, defining characteristics like ID, the number of children, the same attributes, etc. The data points for comparison vary depending on the types of nodes.

It takes one argument, which is the node that we want to compare with.

For example, given the following HTML:

<div id='foo'>  
  foo  
</div>  
<div id='bar'>  
  bar  
</div>

Then we can write the following JavaScript to check if the div with ID foo and the div with ID bar are the same:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');
console.log(foo.isEqualNode(bar));

The console.log should log false since they don’t have the same ID or content.

On the other hand, if we have the following HTML:

<div class='foo'>  
  foo  
</div>  
<div class='foo'>  
  foo  
</div>

Then we can compare them by writing:

const foo1 = document.querySelector('.foo:nth-child(1)');  
const foo2 = document.querySelector('.foo:nth-child(2)');
console.log(foo1.isEqualNode(foo2));

and the console.log should log true since they have identical class value and content.

isSameNode

The isSameNode method checks whether 2 Node objects reference the same node.

It takes one argument, which is the other node to test and returns a boolean indicating whether they’re the same.

For example, given the following HTML:

<div>  
  foo  
</div>

We can call isSameNode by writing:

const div = document.querySelector('div');console.log(div.isSameNode(div));

On the other hand, if we have:

<div>  
  foo  
</div>  
<div>  
  foo  
</div>

Then the following call to isSameNode will return false :

const div1 = document.querySelector('div:nth-child(1)');  
const div2 = document.querySelector('div:nth-child(2)');console.log(div1.isSameNode(div2));

This is because even though they look the same, they don’t reference the same node.

normalize

The normalize method cleans up the sub-tree of a Node by removing adjacent text nodes.

For example, if we have the following HTML:

<div>  
  foo  
</div>

Then before call normalize, we get that there’re 3 child nodes inside the div element after add 2 more text nodes to it as child nodes of the div. After we called it, it’s reducing the number of child nodes to 1.

We call as in the following code:

const div = document.querySelector('div');  
div.appendChild(document.createTextNode("foo "));  
div.appendChild(document.createTextNode("bar"));console.log(div.childNodes.length);  
div.normalize();  
console.log(div.childNodes.length);

The first console.log should log 3 and the second one should log 1.

removeChild

To remove the child node from a given node, we can use the removeChild method to remove the child from the DOM tree.

It takes a child element of the given node as an argument and then returns the node that was removed from the DOM tree.

For example, given that we have the following HTML:

<div id='foo'>  
  foo  
  <div id='bar'>  
    bar  
  </div>  
</div>

We can remove the div with ID bar by writing:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');foo.removeChild(bar);

Then we should no longer see ‘bar’ in the browser window.

It only works with child nodes. For example, if we have:

<div id='foo'>  
  foo  
</div>  
<div id='bar'>  
  bar  
</div>

and run the same code, then we’ll get the error ‘Uncaught DOMException: Failed to execute ‘removeChild’ on ‘Node’: The node to be removed is not a child of this node.’

It’ll also throw an error if the node doesn’t exist.

replaceChild

The replaceChild method replaces the current one with the second one given in the parameter.

For example, if we have the following HTML:

<div id='foo'>  
  foo  
  <div id='bar'>  
    bar  
  </div>  
</div>

Then we can create a new element to replace the div with ID bar:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');const baz = document.createElement('div');  
baz.textContent = 'baz';  
foo.appendChild(baz);foo.replaceChild(baz, bar);

The first argument has the new element and the second argument has the original one.

It also works for using elements that already exist to replace another one that exists.

For instance, if we have the following HTML:

<div id='foo'>  
  foo  
  <div id='bar'>  
    bar  
  </div>  
  <div id='baz'>  
    baz  
  </div>  
</div>

Then we can write:

const foo = document.querySelector('#foo');  
const bar = document.querySelector('#bar');  
const baz = document.querySelector('#baz');
foo.replaceChild(baz, bar);

to replace the div with ID bar with the one with ID baz.

The DOM traversal and manipulation methods are very useful. We can use them to check if they have the same content or referencing the same element. Also, there’re methods to replace nodes and clean up extraneous text nodes. There are also handy methods for inserting nodes and removing nodes.

Categories
Angular JavaScript

Angular Component Lifecycle Hooks Overview

Angular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll take a look at the lifecycle hooks of an Angular component.

Lifecycle Hooks

Angular components have a lifecycle managed by Angular.

Angular offers lifecycle hooks to provide visibility to key events of a component and act when a lifecycle event occurs.

We can implement one or more lifecycle interfaces in our component so that we can make sure our components run code during certain lifecycle events.

No directive or component will implement all lifecycle hooks. Angular only calls a directive or component if it’s defined.

For example, we can implement the OnInit interface to run some code when the component is initialized as follows:

import { Component, OnInit } from "@angular/core";

@Component({  
  selector: "app-root",  
  templateUrl: "./app.component.html",  
  styleUrls: ["./app.component.css"]  
})  
export class AppComponent implements OnInit {  
  ngOnInit() {  
    console.log("init");  
  }  
}

In the code above, we imported the OnInit interface and then implemented the ngOnInit method.

Lifecycle Sequence

Angular calls the lifecycle hook methods in the following sequence at specific moments.

ngOnChanges()

Responds when Angular sets or resets data-bound input properties. This method receives a SimpleChanges object of the current and previous property values.

It’s called before ngOnInit and whenever one or more data-bound input properties change.

ngOnInit()

Initializes the component or directive after Angular displays the data-bound properties and sets the directive or component’s input properties.

It’s called once after the first ngOnChanges.

ngDoCheck()

Detect and act upon changes that Angular can’t or won’t detect on its own.

It’s called during every change detection run and immediately after ngOnChanges and ngOnInit.

ngAfterContentInit()

Runs after Angular projects external content into the component’s view or the view that the directive is in.

It’s called once after the first ngDoCheck.

ngAfterContentChecked()

Runs after Angular checks the content projected into the directive or component.

It’s called after ngAfterContentInit and every subsequent ngDoCheck.

ngAfterViewInit()

Runs after Angular initializes the component’s view and child views or the view the directive is in.

It’s called once after the first ngAfterContentChecked.

ngAfterViewChecked()

Runs after Angular component checks the component’s view and child views or the view that a directive is in.

It’s called after the ngAfterViewInit and every subsequent ngAfterContentChecked.

ngOnDestroy()

Cleans up just before Angular destroys the directive or component. Unsubscribe Observables and detach event handlers to avoid memory leaks.

It’s called just before Angular destroys the directive or component.

Interfaces are Optional

Interfaces are optional since it doesn’t appear in the JavaScript since JavaScript doesn’t have interfaces.

The hooks are just called if they’re defined.

However, implementing them means that we won’t forget to implement the hooks that we want to implement.

Other Angular Lifecycle Hooks

Other Angular subsystems may implement their own hooks.

3rd party libraries may also implement their hooks.

OnInit()

Use ngOnInit to perform complex initialization shortly after construction or to set up the component after Angular sets the input properties.

Therefore, we shouldn’t fetch data in the constructor. We should do no more than initialize local variables with simple values in the constructor.

OnDestroy()

We should put cleanup logic in ngOnDestroy. Anything that must be run before Angular destroys the directive should be in here.

This is a place to free resources that won’t be garbage collected automatically.

We should unsubscribe from Observable and DOM events here.

OnChanges()

Whenever input properties change, the ngOnChanges hook is called.

The old and new values are in the SimpleChange object that’s passed into the parameter.

DoCheck()

Use ngDoCheck to detect and act upon changes that Angular doesn’t catch on its own.

Usually, changes from unrelated data elsewhere on the page isn’t detected by Angular, so we have to run something here to update those values.

AfterViewInit() and AfterViewChecked()

AfterViewInit is called after the component or directive is loaded along with the child views.

AfterViewChecked is called right after ngAfterViewInit.

AfterContent Hooks

AfterContent hooks are similar to AfterView hooks. The difference is that AfterView hooks concern ViewChildren , which are child components whose element tags appear within the component’s template.

AfterContent hooks concern ContentChildren, which are child components that Angular projected into the component.

Conclusion

We can use the hooks to run code during various stages of the Angular component or directive’s lifecycle.

They run in a sequence. We can implement them by implementing the interfaces or just define the methods with the given names in our component or directive class.

Categories
JavaScript Vue

List Rendering with Vue.js — Array Change Detection

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 array change detection capabilities.

Array Change Detection

Vue wraps the following array mutation methods so that when these methods are called, a view update will be triggered. They’re the following:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

For example, if we have a button that triggers a push of a new item to an array, it’ll update:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: [{ name: "Joe" }, { name: "Jane" }]  
  },  
  methods: {  
    addPerson() {  
      this.persons.push({ name: "Mary" });  
    }  
  }  
});

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="addPerson">Add Person</button>  
      <div v-for="person in persons">  
        {{person.name}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get:

JoeJane

When the array is first rendered. Then when we click Add Button, we get:

JoeJaneMary

since we called the addPerson method with the button click, which called the push method to add a new entry.

Replacing an Array

Non-mutating array method are ones that always return a new array. We can replace the original array with the returned array to trigger a view update.

Vue doesn’t re-render the list from scratch when we update the array. Instead, it’ll try to maximum DOM element reuse.

For example, if we have the following:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: [{ name: "Joe" }, { name: "Jane" }, { name: "Mary" }]  
  },  
  methods: {  
    filterPerson() {  
      this.persons = this.persons.filter(p => p.name !== "Joe");  
    }  
  }  
});

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="filterPerson">Filter Person</button>  
      <div v-for="person in persons">  
        {{person.name}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then when our page first loads, we get the following names:

JoeJaneMary

Once we click Filter Person, we get:

JaneMary

since we called the filterPerson method with the button click, which called the filter method to return an array with the filtered items.

Then the new array is assigned to this.persons and then the view is refreshed.

Catches

Vue can’t detect directly assign an item to an array by setting the index or when the length of the array is modified.

So:

vm.item[indexOfItem] = 'item';

and:

vm.items.length = 2;

won’t be picked up by Vue and update the view with updated data.

We can either call Vue.set or splice to set an entry to a given index.

Vue.set

For example, we can write the following code:

src/index.js

new Vue({  
  el: "#app",  
  data: {  
    persons: ["Joe", "Mary"]  
  },  
  methods: {  
    addPerson() {  
      Vue.set(this.persons, 5, "Jane");  
    }  
  }  
});

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="addPerson">Add Person</button>  
      <div v-for="(person, index) in persons">  
        {{index}} - {{person}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

When the page first loads, we get the following entries rendered:

0 - Joe1 - Mary

Then when we click Add Person, we get:

0 - Joe1 - Mary2 -3 -4 -5 - Jane

Splice

We can do the same with splice , but the length has to be set first:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: ["Joe", "Mary"]  
  },  
  methods: {  
    addPerson() {  
      this.persons.length = 5;  
      this.persons.splice(5, 1, "Jane");  
    }  
  }  
});

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="addPerson">Add Person</button>  
      <div v-for="(person, index) in persons">  
        {{index}} - {{person}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get the same results as before.

We can also use splice to set the length of the array and trigger view update. To do this, we can write:

this.persons.splice(5);

vm.$set

We can call vm.$set as follows, which is the same as Vue.set except that it’s available to the Vue instance instead of a global object.

For example, we can write:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: ["Joe", "Mary"]  
  },  
  methods: {  
    addPerson() {  
      this.$set(this.persons, 5, "Jane");  
    }  
  }  
});

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="addPerson">Add Person</button>  
      <div v-for="(person, index) in persons">  
        {{index}} - {{person}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Conclusion

Some array changes will trigger view updates automatically.

Array mutation method calls are watched to update the view.

Array methods that return new arrays have to treated differently from mutation methods. The returned value has to be assigned to the original variable to trigger a view update.

To trigger view update when we assign an entry to an array index, we can use splice , Vue.set , or this.$set / vm.$set.

To set the array’s length and trigger view updates, we can also call splice .