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 .

Categories
JavaScript Vue

Vue.js Transition Effects — Transitioning Between Elements

Transitioning Between Elements

With Vue, we can transition elements between v-if and v-else .

When toggling between elements that have the same tag name, we must tell Vue that they’re distinct elements giving them unique key attributes. Otherwise, the Vue compiler will only replace the content of the element for efficiency.

It’s always a good idea to key multiple items within a transition element.

For example, we can transition between 2 p elements as follows:

src/index.js :

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

src/style.css :

.fade-enter-active,  
.fade-leave-active {  
  transition: opacity 0.5s;  
}  
.fade-enter,  
.fade-leave-to {  
  opacity: 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>  
    <link  
      rel="stylesheet"  
      type="text/css"  
      href="./src/styles.css"  
      media="screen"  
    />  
  </head>  
  <body>  
    <div id="app">  
      <button v-on:click="show = !show">  
        Toggle  
      </button>  
      <transition name="fade">  
        <p v-if="show" key="hi">hi</p>  
        <p v-else key="bye">bye</p>  
      </transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we should see fade effects when we transition between ‘hi’ and ‘bye’.

We added key attributes to each so they’ll always be rendered from scratch.

Also, we can use the key attribute to transition between different states of the same element.

We can do that as follows:

src/index.js :

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

src/styles.css :

.fade-enter-active,  
.fade-leave-active {  
  transition: opacity 0.5s;  
}  
.fade-enter,  
.fade-leave-to {  
  opacity: 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>  
    <link  
      rel="stylesheet"  
      type="text/css"  
      href="./src/styles.css"  
      media="screen"  
    />  
  </head>  
  <body>  
    <div id="app">  
      <button v-on:click="show = !show">  
        Toggle  
      </button>  
      <transition name="fade">  
        <p v-bind:key="show">  
          {{ show ? 'hi' : 'bye' }}  
        </p>  
      </transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we replaced:

<transition name="fade">  
  <p v-if="show" key="hi">hi</p>  
  <p v-else key="bye">bye</p>  
</transition>

with:

<transition name="fade">  
  <p v-bind:key="show">  
    {{ show ? 'hi' : 'bye' }}  
  </p>  
</transition>

Everything else remained the same.

The transition can be done between any number of elements with v-if or binding a single element to a dynamic property.

For example, we can write:

src/index.js :

new Vue({  
  el: "#app",  
  data: { states: ["foo", "bar", "baz"], state: "", index: 0 },  
  methods: {  
    rotate() {  
      this.index = (this.index + 1) % this.states.length;  
      this.state = this.states[this.index];  
    }  
  }  
});

src/styles.css :

.fade-enter-active,  
.fade-leave-active {  
  transition: opacity 0.5s;  
}  
.fade-enter,  
.fade-leave-to {  
  opacity: 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>  
    <link  
      rel="stylesheet"  
      type="text/css"  
      href="./src/styles.css"  
      media="screen"  
    />  
  </head>  
  <body>  
    <div id="app">  
      <button v-on:click="rotate">  
        Rotate  
      </button>  
      <transition name="fade">  
        <p v-bind:key="state">  
          {{ state }}  
        </p>  
      </transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

We can rotate to different state value from the states array. Then we see transition each time the value of state changes.

Also, we can write out the v-if statements for each item as follows:

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>  
    <link  
      rel="stylesheet"  
      type="text/css"  
      href="./src/styles.css"  
      media="screen"  
    />  
  </head>  
  <body>  
    <div id="app">  
      <button v-on:click="rotate">  
        Rotate  
      </button>  
      <transition name="fade">  
        <p v-if="state === 'foo'" key="foo">  
          foo  
        </p>  
        <p v-if="state === 'bar'" key="bar">  
          bar  
        </p>  
        <p v-if="state === 'baz'" key="baz">  
          baz  
        </p>  
      </transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The rest are unchanged.

Transition Modes

We can set the transition modes to control how old elements are transition to new elements.

There’re 2 transition modes:

  • in-out — new element transitions in first, then the current element transitions out
  • out-in — current element transitions our first and then the new element transitions in

For example, we can use it as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: { states: ["foo", "bar", "baz"], state: "", index: 0 },  
  methods: {  
    rotate() {  
      this.index = (this.index + 1) % this.states.length;  
      this.state = this.states[this.index];  
    }  
  } 
});

src/styles.css :

.fade-enter-active,  
.fade-leave-active {  
  transition: opacity 0.5s;  
}  
.fade-enter,  
.fade-leave-to {  
  opacity: 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>  
    <link  
      rel="stylesheet"  
      type="text/css"  
      href="./src/styles.css"  
      media="screen"  
    />  
  </head>  
  <body>  
    <div id="app">  
      <button v-on:click="rotate">  
        Rotate  
      </button>  
      <transition name="fade" mode="out-in">  
        <p v-bind:key="state">  
          {{ state }}  
        </p>  
      </transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

With the mode attribute added, we no longer have the jumpiness that the previous example has when transitioning between elements since old and new elements won’t exist on the same screen.

Conclusion

We can add the transition mode to prevent undesirable results from the old and new elements existing together.

Also, we should add a key attribute to each element that’s transitioned to that they’re rendered from scratch. It works for v-if and dynamic elements.