Options and Composition API

Learn how to distinguish between the options and the composition API in Nuxt.

When the Composition API was announced, it received some negative feedback from the Vue community, mainly due to a misunderstanding of what the Composition API was intended to achieve. Some thought that the Vue.js core team was replacing the options API, which includes things like the data, methods, and computed sections and replacing it with a whole new thing to learn.

This is not the case; the way we write Vue code with the Options API is still completely valid, but the Composition API is provided as an additional way of writing Vue apps. Both can be used together in the same components if needed, making refactoring much smoother.

The motivation behind the Composition API was to make it much easier to organize, maintain, and reuse our code. Reusable components take us so far, but JavaScript inside of these components can be organized much better.

Where should Options or Composition code be used?

We can write any of the upcoming Options API or Composition API code into any Nuxt page or component.

Options API

The Options API is how we have written our code in both Nuxt and Vue.js. It uses groups for data, methods, computed properties, and watch to name a few. Here is a typical Options API setup; in our script, the Options API groups things such as our data (state), methods, watchers, and computed properties:

<script>
export default {
	data() {
		return {
			user: {},
			products: [],
			basket: [],
		};
	},
	mounted() {
		this.getUser(), 
		this.getProducts();
	},
	methods: {
		getUser() {
			this.user = { name: "bob" };
		},
		getProducts() {},
		updateBasket(item) {},
	},
	computed: {
		showBasket() {
			return this.basket.length > 0 ? true : false;
		},
	},
	watch: {
		user() {},
	},
};
</script>

<template>
	<h1>Options API Code</h1>
	<p>{{ user }}</p>
</template>
Page displaying Options API code

This code can be grouped into three sections:

  • User-related code:

    • Line 5: This is a user variable to store the current user.

    • Lines 15–17: This is a function to get the current user; for this simple example, it will set a user name.

    • Line 11: Once the component has mounted, the getUser function will be called.

    • Line 27: We run a method when the user data changes (watch).

  • Product-related code

    • Line 6: This is a products array to store the products.

    • Line 18: This is a function to get the current products.

    • Line 12: Once the component has mounted, the getProducts function will be called.

  • Basket-related code:

    • Line 7: This is a basket array to store the products added to the basket.

    • Line 19: This is a function to update the basket contents.

    • Lines 21–25: The showBasket code will run to display the basket if it is not empty. Since this is wrapped in a computed property, this will be re-run if any of the data inside is updated.

In this small example, things should be fine to maintain, but bear in mind this is a much-shortened version; the methods have little or no code inside. Realistically, it will require much more code to make it all work.

One of the problems this has is organization. It would be nice to group the related code as highlighted above, especially in much larger components, rather than jumping to different sections.

Another issue is reusability. Reusable components can take us so far, but we could end up writing the same code in multiple components, which takes more time and means maintaining multiple code sections that do the same thing.

Composition API

These are some of the things the Composition API aims to resolve. We can now write our code more like traditional JavaScript, group our code more easily, and create a system to reuse code. Writing Options API code with regular JavaScript:

let user = {};
function getUser() {
user = { name: "bob" };
}
let products = [];
function getProducts() {}
const basket = [];
const showBasket = basket.length > 0 ? true : false;
function updateBasket(item) {}
  • Lines 1–2: We create a user object and a function to update the name.

  • Lines 6–7: We create a products array and a function to fetch the products.

  • Line 9: This is a basket array created to store products added to the basket.

  • Line 10: We create a variable to be set to true when there are items in the basket.

  • Line 11: This function will update the basket when an item is selected.

There may be some code that overlaps down the line, but you can already see that this grouping together can make our code more organized.

But where do we put all of this code? Using Vue 3 and Nuxt 3, we have two options. In our script, we place our code inside our export default section and wrap it in a setup function:

<script>
export default {
setup() {
const user = {}
function getUser() {}
const products = []
function getProducts() {}
const basket = []
const showBasket = basket.length > 0 ? true : false
function updateBasket(item) {}
return { user, getUser, products, getProducts, ...}
}
}
</script>
  • Line 2: We create an export default wrapper for all of our Nuxt code.

  • Line 14: We return any values we want to be made available to use in the template.

Returned values will be made available to use in our template:

<template>
<p>Welcome {{ user }}</p>
<button @click="getUser">fetch user</button>
</template>

We are not just leaving Vue behind here either with this JavaScript syntax. We still have access to familiar things, such as lifecycle hooks. Using the on prefix, we see onMounted to get our users and products when the component is mounted to the DOM.

Lifecycle hooks are used to run functions at various stages, from before the component is created to after it has been destroyed. We can run functions to fetch data as soon as a component is ready, for example, or even to stop listening for events when a component is no longer used. They are also useful to perform actions when any piece of data has been updated. An example could be to re-fetch the current user when a change of user is detected. And also computed properties are as follows:

<script>
export default {
setup() {
const user = {}
function getUser() {}
onMounted(getUser)
const products = []
function getProducts() {}
onMounted(getProducts)
const basket = []
const showBasket = computed(() => basket.length > 0 ? true : false)
function updateBasket(item) {}
return { user, getUser, products, getProducts, ...}
}
}
</script>
  • Line 7: We get the user when the component mounts to the DOM.

  • Line 12: We get the products when the component mounts to the DOM.

  • Line 14: The computed value updates when a value inside changes.

Here is an example of the Options API code converted to the Composition API:

<script>
export default {
	setup() {
		let user = {};
		function getUser() {
			user = { name: "bob" };
		}
		onMounted(getUser);

		let products = [];
		function getProducts() {}
		onMounted(getProducts);

		const basket = [];
		const showBasket = computed(() => (basket.length > 0 ? true : false));
		function updateBasket(item) {}

		return { user, getUser, products, getProducts };
	},
};
</script>
<template>
	<h1>Composition API Code</h1>
	<p>{{ user }}</p>
</template>
Page displaying the composition API code

When we run the code, we can see that the user name is not displayed in the template. A difference between this and the Options API example is that values in the data section (Options API) are reactive. This means they will be updated after a change occurs. This is different from the Composition API example, where this is not the case. The user variable we create is only reactive when combined with a Vue.js reactive wrapper, such as ref. This behavior more closely resembles JavaScript.

With Vue.js, we would also need to import the required modules from the Vue library:

<script>
import { onMounted, computed } from 'vue'
// ...
</script>

However, with Nuxt 3, these imports are automatic due to auto-import, meaning they can be left out.