new Vue();New vue instance. Core of each piece of code. Instances have one major job: contol thier own template of code w/c rendered to the screen.
new Vue(
el: ''
);el is a very important reserved property of vue. The value is setup w/c part of the html code to under control by the view instance.
Example:
<div id="app">
<p>Hello vue!</p>
</div>new Vue(
el: '#app'
);new Vue(
el: '#app',
data: {
title: 'Hello vue'
}
);data stores all the data we want to use in this view instance. {{ }} is a special syntax to render variables/keys from data the property.
Example:
<div id="app">
<p>{{ title }}</p>
</div><div id="app">
<input type="text" v-on:input="changeTitle">
<p>{{ title }}</p>
</div>directive:[event]
v-on directive. This will listen to some event.
Where we setup our methods either from the template or within view instance.
Example:
new Vue(
el: '#app',
data: {
title: 'Hello vue'
},
methods: {
changeTitle: function(event) {
this.title = event.target.value;
}
}
);this is referring to the data object. Basically it got access to all properties in data and methods stored in methods.
Let's start diving super deep into Vue!
-
Interacting with the DOM (Templates)
How we can interact with the dom, with the data, bind to html attributes, listen events etc.
-
Understanding the VueJS Instance
Core object, how does it work, w/c lifecyle does it follow ?
-
Provides easy-to-use and quickly to get started with templates.
-
A key concept of VuewJS. Build up applications thru components. Multiple modules on basics and advance concepts of components.
-
How we can handle user inputs, use different form elements, create our own form control.
-
How can it help us on our application?
-
Learn how vuejs makes this very easy. How ti supports us animating or dynamic contents or switching component to another.
-
Reach out to server.
-
How router works, how to set it up, how to use child routes and pass parameters ets.
-
Managing the state in more complex apps, can quickly become a problem. VueX great additional package for state management.
-
Deployment. How can we shift the application to a server.
VueJS creates a template based on our html code, stores that internally, and then uses the template to create the real html code, w/c then is rendered as the DOM.
All properties in data and methods are accessible in your html. Proprties that should return something (string) that can be output on the html.
<div id="app">
<p>{{ changeText() }}</p>
</div>new Vue(
el: '#app',
data: {},
methods: {
changeText: function() {
return 'Hello!';
}
}
);In javascript code accessing variables; the data and method properties should use this. this proxies all the vuejs properties, give us access anywhere under vue instance code.
new Vue(
el: '#app',
data: {
title: 'Hello!'
},
methods: {
getText: function() {
return this.title;
}
}
);We can't just use curly braces {{}} in html element attributes. The normal behaviour of html encodes / parsed them. We can only use them on where we put normal html texts.
We can still bind element attribute though, introducing a directive v-bind.
v-bind:[attribute]
<div id="app">
<a v-bind:href="link">Google</a>
</div>new Vue(
el: '#app',
data: {
link: 'http://google.com'
}
);An instruction you placed in your code. Vue shifts with builtin directives.
Vue directive w/c rendered an element attached to it rendered only once. It keeps the initial value before it is overwritten.
<p v-once>{{ title }}</p>Rendering html string on html do not read the string as html element. This is to avoid xss attacks.
To allow to render html we can use another directive v-html. But please this carefully.
<div id="app">
<span v-html="htmlLink"></span>
</div>new Vue(
el: '#app',
data: {
htmlLink: '<a href="http://google.com">google</a>'
}
);Using v-on directive to listen on events. Bind something in a template to pass data to it. Listen something and recieve something from a template.
v-on:[event]="fn"
<div id="app">
<button v-on:click="increase"></button>
<span>{{ counter }}</span>
</div>new Vue(
el: '#app',
data: {
counter: 0
},
methods: {
increase: function() {
this.counter ++;
}
}
);Every event holds an event object w/c can be accessed with our listeners.
<div id="app">
<span v-on:mousemove="updateCoordinates">Coordinates {{ x }} / {{ y }}</span>
</div>new Vue(
el: '#app',
data: {
x: 0,
y: 0
},
methods: {
updateCoordinates: function(event) {
this.x = event.clientX;
this.y = event.clientY;
}
}
);You can pass arguments to your listener together the event object. VueJS fetches the default event and stores it to a protected variable $event;
<div id="app">
<button v-on:click="increase(2, $event)"></button>
<span>{{ counter }}</span>
</div>new Vue(
el: '#app',
data: {
counter: 0
},
methods: {
increase: function(step, event) {
this.counter += step;
}
}
);You can anytime modify an event like event.stopPropagation() fn, for example this listener is a child of a mousemove event listener.
Event Access Modifiers. Allows to modify an event.
v-on:[event].[modifier]
Ex:
This is the same function as the event.stopPropagation()
v-on:mousemove.stopYou can also chain modifiers by adding .
This will event.stopPropagation() and also event.preventDefault()
v-on:mousemove.stop.preventKey Access Modifiers. keyboard event modifers
Ex:
Only execute function after entering after keyup.
v-on:keyup.enter="fn"
<div id="app">
<input v-on:keyup.enter="alertMe" />
</div>new Vue(
el: '#app',
methods: {
alertMe: function() {
alert('submit');
}
}
);Write js expression on event bind value
Ex:
v-on:click="counter++"
Write js expression on display value (template expression)
Ex:
{{ counter * 2 }}
{{ counter > 10 ? '> 10' : '< 10' }}
Both listen and output data. Using v-model to listen to keyboard event and output at the same time.
<div id="app">
<input type="text" v-model="name"/>
<p>{{ name }}</p>
</div>new Vue(
el: '#app',
data: {
name: ''
}
);Computed properties are methods that is used in template just like a variable data. Use computed key, this is where we store computed data outputed in the template.
Important Note: Computed properties are cached when results is still the same. meaning, the computed method will not be executed unless the return value could change.
<div id="app">
<p>{{ output }}</p>
</div>new Vue(
el: '#app',
data: {
counter: 0
},
computed: {
output: function() {
// Executed only when the value changes
return this.counter > 5 ? '> 5' : '< 5'
}
}
);watch (property): Executes code upon data changes. Key properties on watch refers to the key properties of data.
new Vue(
el: '#app',
data: {
counter: 0
},
watch: {
counter: function(value) {
// execute whenever the counter data changes
// do something here
}
}
);-
v-oncan be replace with@sign Ex:@click -
v-bindcan be replace with:Ex::href
Using click event to change the data and listen to that with class binding. Binding class property should expect an objec or just a class name, key as the class name and the value (boolean) to decide if needs to be display.
<div id="app">
<p :class="{red: attachedRed}"></p>
</div>Bound class value can be also put into computed property, making it cleaner on the element.
<div id="app">
<p :class="colorClasses"></p>
</div>new Vue(
el: '#app',
data: {
attachedRed: false,
},
computed: {
colorClasses: function() {
return {
// [class name]: [is enable]
red: this.attachedRed,
blue: !this.attachedRed
}
}
}
);Bound class value can be just a css class name.
<div id="app">
<p :class="color"></p>
</div>new Vue(
el: '#app',
data: {
color: 'green',
}
);Important We can also attached multiple class names on class bind.
<p :class="[color, 'red']"></p>
And object can added also on the list:
<p :class="[color, {red: attachedRed}]"></p>
All classes with different types will just be merged into one list of classnames.
Bind to a style property of an element and pass an object.
<div id="app">
<!-- object key can be same with defualt style names: 'background-color' -->
<p :style="{backgroundColor: color}"></p>
</div>The object value can be assigned as a computed data, making it more clean on the element.
new Vue(
el: '#app',
computed: {
myStyle: function() {
return {
backgroundColor: 'red',
margin: 'auto'
}
}
}
);You can also add a value as an array: could be a variable data or object.
<div id="app">
<!-- object key can be same with defualt style names: 'background-color' -->
<p :style="[myStyle, {backgroundColor: color}]"></p>
</div>Displaying array of data and conditionally display elements.
v-if A directive. Attaches / detaches elements to the DOM.
<!-- data: show -->
<p v-if="show">You can see me!</p>v-else
<!-- data: show -->
<p v-else>Otherwise me!</p>v-else-if serves as an else if block for v-if. It can also be chained multiple times:
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>Using html5 template tag, to attach multiple elements under condition. template tag is not rendered on the html, we can use this as a wrapper.
<template v-if="show">
<p></p>
<span></span>
</template>v-show directive only hides an element. When set to false, it will automatically adds a style="display: none;" on the element.
<template v-show="show">
<p></p>
</template>Loop the list data on the template.
new Vue(
el: '#app',
data: {
ingredients: ['meat', 'egg', 'pepper']
}
)<div id="app">
<ul>
<li v-for="ingredient of incredients">{{ ingredient }}</li>
</ul>
</div><div id="app">
<ul>
<!-- the firsr arg is an ITEM of array, second one is the INDEX -->
<li v-for="(ingredient, index) in incredients">{{ ingredient }} {{ index }}</li>
</ul>
</div>new Vue(
el: '#app',
data: {
persons: [
{ name: 'juan', age: '25'},
{ name: 'joe', age: '25'}
]
}
)<div id="app">
<ul>
<li v-for="person in persons">
<!-- loop key value pairs og an object, will get the value automatically -->
<span v-for="value in person">{{ value }}, </span>
<!-- juan, 25, -->
<!-- can also get the key and index-->
<!-- <span v-for="(value, key, index) in person">{{key}}: {{ value }}</span> -->
</li>
</ul>
</div>Convenient syntax if we want to count numbers.
<div id="app">
<div v-for="n in 10">{{ n }}</div>
</div>Assign a unique key for each of the element to keep track of the position. Making it safe when we dynamically updates specific items. The elements won't reordered, VueJS already stores/remembers each unique element.
<div id="app">
<ul>
<li v-for="ingredient in incredients" :key="ingredient">{{ ingredient }} {{ index }}</li>
</ul>
</div>How does this work, how it is created ?
Vue instance is the middle man between the html code (DOM) and business logic. Pack all BL in the vue instance either in the data properties, methods or computed properties and others.
Can create multiple instance. You acn only access properties with this to its own instance.
<div id="app1">
<span>{{ title }}</span>
</div>
<div id="app2">
<span>{{ title }}</span>
</div>new Vue({
el: '#app1',
data: {
title: 'First instance'
}
});
new Vue({
el: '#app2',
data: {
title: 'Second instance'
}
});By assigning a variable to an instance we can directly access/changed its properties from other instances.
// vm - view model, vue naming conventions
var vm1 = new Vue({
el: '#app1',
data: {
title: 'First instance'
}
});
new Vue({
el: '#app2',
data: {
title: 'Second instance'
},
methods: {
changeTitle: function() {
vm1.title = 'changed!';
}
}
});Vue instance properties are just converted to native object properties. Vue proxies all properties in the object so they are listened, watched. Manually adding prop to an object, vue doesn't setup any proxy to it.
vm object consists properties $el, $data.
vm.$data.title;
vm.$el;$el is the actual html element
$data is the actual data object we assigned to data property of vue instance.
VueJS ref (not an official html attr) is a key w/c is recognized by VueJS. $refs, we can access all elements we setup.
Important: Adding/Changing properties directly the DOM, those properties are not reactive, not part of the vuejs template. VueJS just overrides them and use what was setup on the instance.
<div id="app1">
<span ref="myTitle">{{ title }}</span>
</div>var vm1 = new Vue({
el: '#app1',
data: {
title: 'First instance'
}
showRefs: function() {
console.log(this.$refs.myTitle);
// { title: element }
}
updateMyTitle: function() {
this.$refs.myTitle.innerText = 'updated by native html fn'
}
});Using VueJS instance property $mount() to manually mount an element reference other than on el property.
Properties with $ are native apis of vue.
vm.$mount('#app');<div id="app">
</div>var vm = new Vue({
template: `<h1>title</h1>`
});
vm.$mount('#app');The template string will be mounted to id app on the html.
Mounting a template can be manual also:
// first mount the tempalte (no target element here)
vm.$mount();
// do the native js
document.getElementById('app').appendChild(vm.$el);// the second acts like object in a vue instance
Vue.component('hello', {
template: '<h1>hello</h1>'
});<hello></hello>
<hello></hello>Using template property (string) limits ide support like syntax highlights etc.
Vue Instance <--- watch ---> Virtual DOM --> update ? --> DOM
Virtual DOM - representation of a real DOM, a copy parsed in javascript and quick to be access. To w/c property did change or w/c part of the template is affected, that will goes out to the real DOM and updated that part.
How to add this lifecycle methods on the vue initialization.
new Vue({
el: '#app',
data: {
title: 'The VueJS instance'
},
beforeCreate: function() { /* before this instance is created */ },
created: function() { /* this instance is created */ },
beforeMount: function() { /* before the compiled template displayed to DOM */ },
mounted: function() { /* the template is mouted to DOM */ },
beforeUpdate: function() { /* before this instance is being updated by property changes */ },
updated: function() { /* instance is updated */ },
beforeDestroy: function() { /* before this instance is destroyed */ },
destroyed: function() {}
});Development Workflow. Developing a project should come with set of tools to build effeient apps. Compilers for vue templates, selectors, transpiler for ES6, TS to vanilla js, Sass to css, minifiers are examples for pre-processes before an app is run and ready.
Development Server. Running our app should be fetched from Http. In this case we will need to install NodeJS and vue-cli toolkit. vue-cli handles this already. By running your app locally it will be hosted by local machine and accessing thru http:// already. That won't difficult if we publish the app to a production server.
Using vue-cli@2
$ sudo npm install -g vue-cli4 templates used:
- simple - Vue CDN, index.html
- webpack-simple - Basic Webpack Workflow
- webpack - Complex Webpack Workflow (Incl. Testing)
- browserify - Browserify Workflows
Create a Vue project
vue init [template] [project-name]
$ vue init webpack my-app
$ cd my-app
$ npm install
$ npm run devVue file has a <template>, <script> and <style>. During build process with webpack, it will be compile to javascript code.
There is no need to specify what template to be used on the <script>. Vue automatically uses the template / html on that the same file.
The object behaves like a vue instance, we can use it as component.
$ npm run buildThis will create a /dist folder. Which holds your bundle and it will also have an optimization in place there.
A major piece of VueJS.
Using the vue instance targeting an id or a class, repeating the element with this attribute value, vue won't repeat it, it will only use the first one. This where we can use Component instead.
Think of a component as an HTML element.
<div id="app">
<my-cmp></my-cmp>
<!-- you can repeat it of course -->
<my-cmp></my-cmp>
</div>// The 1st arg is your component name, will use just like a tag name in html.
// NOTE: Prefixing component name, making it more unique. So it won't interfere with existing one's like third parties
// The 2nd arg is an object w/c represents the component.
// The same object we're passing on a vue instance. Basically component extends Vue instance.
Vue.component('my-cmp', {
// hence, data property as an object will interfere other objects. Vue will prompt an error.
// component data is required to be a Function that returns an Object
data: function() {
return {
status: 'Critical'
}
},
template: '<p>status: {{ status }}</p>'
});// Using shared data source will update all components and its data.
// !WARNING: This is an issue. vue data is mutable, and JS iby default will pass the variable reference.
// Don't do this! return an object literal directly from the data function instead.
var data = { status: 'Critical' };
Vue.component('my-cmp', {
data: function() {
return data;
},
template: '<p>status: {{ status }}</p>'
methods: {
changeStatus: function() {
// vue automatically handles accessing data directly even if data property actually is a function.
this.status = 'Normal';
}
}
});Global components are accessible anywhere. Using the Vue object and
Vue.component('my-cmp', {});Local components are explicitly assigned in a variable and manually added to a Vue instance under components property.
var myComponent = {} // Same object in the 2nd argument of Vue.component
Vue({
data: {},
components: {
// key as the component name to use in html
'my-cmp': myComponent
}
});Better writing template alternative is to write in the same vue file. Now we won't experience the limitation of a template in a vue instance as a string.
Example below is an single file component (or vue instance in the ex)
import Vue from 'vue';
import App from './App.vue';
// A root vue instance
new Vue({
el: '#app',
render: h => h(App) // using render function to replace everything
})<!-- Put this in the HTML section in JSFiddle! -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<p>Status: {{ status }}</p>
</div>
<script>
export default {
// writing component's data should be in function type that returns an object
data: function() {
return {
status: 'Critical'
}
}
}
</script>Component can be created by creating new vue file.
Vue files as we know can have 3 different elements: template, script and style.
Note: template should have one root element!
<template></template>
<script>
export default {
}
</script>
<style></style>The component above did not defined the name of element. We can set its component selectors either locally or globally.
Vue.component('my-home-component', Home); // we set it GLOBALLY, this component is visible and can be use anywhere in the src code.
// else locally, exclusive for this specific instance only.
Vue({
...
components: {
'my-home-component': Home
}
})Using components locally within component (like parent component)
<script>
import ChildComponent from './..';
// ChildComponent - we don't a imply a name here, its up to you. Because we literally don't have a name on the export. But keep it as pascal case!
export default {
components: {
'child-component-1': ChildCompnent // local component for this component only
}
}
</script>For bigger projects, we can structure it by features:
├── src # App source code
│ ├── views # App views
│ │ ├── components # Global components
│ │ │ ├── server # Some specific feature-specific components
│ │ │ └── shared # shared components across all codes
│ │ ├── modules # feature chunked views
│ │ │ ├── registration # Some feature views, grouped toether
│ │ │ ├── dashboard # can have thier own local components
│ │ │ └── public
You can use case sensitive selectors. Because js is case sensitive.
eg.
{
appHeader: component1, // js style component naming, but you don't have to, hmtl component naming (with dash) is likely being used.
'app-footer': component2
}with this selector it can still be use in template as:
<app-header></app-header>vue already handle this.
Any style applied to a component is global, like a global styledsheet.
By adding scoped attribute, then it will be contained on that component only.
<style scoped>
<style>Behind the scenes, this emulates a behaviour known Shadow DOM. The DOM, each element has a DOM behind the element. means like, an h1 tag van have DOM, with its own styles.
Using the scoped, this will add a unique attribute data-v-xxxxxxxx on the template of the component. As identifier that the styles are exclusive only for that template.
div[data-v-xxxxxxxx] {
border: 1px solid red;
}Learn to communicate different pieces of components.
How do we get the data from the parent component?
Transfering data from parent to child, we use something like props -- means properties to be passed.
Implicitly passed props from outside
Parent Component:
Bind the data name to make it dynamic. Without binding, it will just pass the actual string.
<app-user-details :name="name"></app-user-details>Child Component:
<template>
<p>{{ name }}</p>
</template>
<script>
export default {
props: ['name'] // an array of string, string as prop name from outside.
}
</script>Single file template supports camel case prop names like - myName, but on the other hand DOM restricts this, should lowercase and with dash.
Props passed from parent can be access by the child as its normal data.
<script>
export default {
props: ['name'],
methods: {
switchName() {
return this.name.split("").reverse().join("");
// we can normally use the props like its own data.
}
}
}
</script>Validating the type of our props. We can setup the props as object with prop keys expected.
When a prop passed with different type, Vue will give a warning.
export default {
props: {
name: String
}
}Additional validations for a prop can be nested into objects.
export default {
props: {
name: {
type: String,
required: true,
// default: 'Max' // if not required we can set a default also
// whe a type is an object, 'default' key should be a function that returns an objects / array
}
}
}All props passed to a child component are not the reference from the data of that parent component. I f we want to update the parent data when an event is called from child, we can use a custom event.
Parent:
<!-- listening to an event using `v-on`, $event is the special pre-built argument that was passed by the handler -->
<app-user-detail :name="name" @nameWasReset="name = $event"></app-user-detail>Child:
<script>
export default {
props: ['name']
methods: {
resetName() {
this.name = 'Max'; // locally reset the prop name
this.$emit('nameWasReset', this.name); // using builtin $emit fn, we can publish an event handler with argument.
}
}
}
</script>Communication is unidirectional, it means it only travels from parent to children vv. Not the other way like child to child.
Another option a child can communicate to parent.
Parent:
<!-- listening to an event using `v-on`, $event is the special pre-built argument that was passed by the handler -->
<app-user-detail :name="name" :resetFn="resetName"></app-user-detail>Child:
<template>
<button @click="resetFn()"></button>
</template>
<script>
export default {
props: {
name: String,
resetFn: Function // using the prop callback will execute the reference function from the parent component.
}
}
</script>It's not possible! It needs to travel first on parent component to change that data to other siblings. We can use callback or custom events to update the parent data.
Event Bus is a vue instance, it's like a centralized service with vue instance capabilities. To possibly communicate to remote component siblings we can use method like this.
// main.js
export const eventBus = new Vue({});<!-- child 1 -->
<script>
import { eventBus } from './main';
export default {
methods: {
resetName() {
eventBus.$emit('nameWasReset', this.name);
// $emit is a property of vue instance as we know.
}
}
}
</script><!-- child 2 -->
<script>
import { eventBus } from './main';
export default {
methods: {
...
},
created() {
// this is a lifecycle hook.
// we can listen to events during this time
eventBus.$on('nameWasReset', (name) => {
this.name = name; // this will update the local data name.
}); // this will listen to an event
}
}
</script>On the event bus we can also set a methods there, since this is just a vue instance like so:
export const eventBus = new Vue({
methods: {
changeName(name) {
this.$emit('nameWasChanged', name);
}
}
})And just call this method from where this bus was imported:
eventBus.changeName(name);Dive deeper to advance topics and features regarding components.
VueJS offers us a so called slots so we can be able to pass html child elements from the defined component in html
<app-qoute>
<!-- need to these elements to app-qoute component -->
</app-qoute><slot> is a reserved vue component. When this is defined, it will display the elements passed from its user component.
<app-qoute>
<p>A wonderful day!</p>
</app-qoute><div>
<!-- the '<p>A wonderful day!</p>' will append here -->
<slot></slot>
</div>- The elements passed to that B component that is inserted to the
slot, will be styled by the B component. WE CAN style the elements from the parent component that is to passed to slots. So that B component will render its defined styles. - Passing a data interpolation to elements that is to be passed will still work.
<app-qoute>
<!-- this works -->
<p>{{ title }}</p>
</app-qoute>Rendering elements to specific slots using named slots.
<app-qoute>
<h1 slot="title">A wonderful day</h1>
<p slot="content">Lorem ipsum></p>
</app-qoute><!-- Qoute -->
<div>
<slot name="title"></slot>
<slot name="content">Lorem ipsum></slot>
</div>Unnamed elements slot, will be automatically rendered to default slot with no name<slot></slot>
If we wanted to have a default content for a slot while it's not yet provided by the parent component. We can just simply add a content on the specific <slot>
<!-- 'The Subtitle' is displayed while there's no named slot from parent component, otherwise the slot content is replaced when occupied -->
<slot name="subtitle">The Sibtitle</slot>It helps distribute components, reusable widgets, switch slides with individual html layout.
Dynamic Components. Using <component> to dynamically sqitch display several components.
<component></component>. This selector is a reserved word. Allows us to add dynamic components.
<template>
<button @click="selectedComponent = 'appAuthor'">Switch to Author</button>
<!-- 'is' atttribute should be bound to data that defines w/c component is active -->
<component :is="selectedComponent"><p>Default Content</p></component>
<!-- the '<p>Default Content</p>' here will be appended to default slot under the active component -->
</template>
<script>
export default {
data() {
return {
selectedComponent: 'appQoute' // the string is interpreted as a selector
}
},
components: {
appQoute: Qoute,
appAuthor: Author
}
}
</script>When components are switched, previous component were destroyed and created again when switch back. The component dies, but there is a way we can override this behaviour.
We can use a reserved keyword (component kind of) with <keep-alive>.
This make sures the compenent is still there and alive. Thus, we keep its local state.
<keep-alive>
<component :is="selectedComponent">
</component>
</keep-alive>We have two lifecycle hooks we can use in dynamic components. On activated (loaded) and deactivated (unloaded away).
For example; A component in dynamic components.
export default {
activated() {
console.log('activated, loaded');
},
deactivated() {
console.log('deactivated, unloaded');
}
}The basics; using v-model to two-way bind an input.
<input type="email" v-model="email" />
<!-- ... -->
<p>Email: {{ email }}</p>export default {
data() {
return {
email: ''
}
}
}Grouping input to one data object.
For example data:
export default {
data() {
userData: {
name: '',
email: '',
age: 27
}
}
}Now form inputs should be:
<input type="email" v-model="userData.email" />
<!-- ... -->
<p>Email: {{ userData.email }}</p>By default data updates update per keystrokes.
v-model.[mod].[mod]Common modifiers:
.lazywill only update the data once done (leaving the input).trim- remove unnecessary white space on beggining ang end.number- force input to be a number
The same with normal inputs to bind the data. With string interpolation or a text value inside textarea won't work as intended.
To keep the multiline from the textarea input as the value on display (string interpolated), we can use normal css style to do this.
<p style="white-space: pre">Message: {{ message }}</p>When there multiple checkboxes on form group, we can bind them with same property data. This will merge the values into an array on a data assigned.
<label for="sendmail">
<input
type="checkbox"
id="sendmail"
value="SendMail"
v-model="sendMail"> Send Mail
</label>
<label for="sendInfomail">
<input
type="checkbox"
id="sendInfomail"
value="SendInfoMail"
v-model="sendMail"> Send Infomail
</label>
<!-- ... -->
<li v-for="item in sendMail">{{ item }}</li>data() {
sendMail: [] // ['SendMail', 'SendInfoMail']
}Making sure we only selected one. The data should be single value.
Everytime we select the pther, the previous will be unselected automatically
data() {
gender: 'Male'
}<label for="male">
<input
type="radio"
id="male"
value="Male"
v-model="gender"> Male
</label>
<label for="female">
<input
type="radio"
id="female"
value="Female"
v-model="gender"> Female
</label>Similarly from the inputs, use v-model to bind the value on on the select input.
When :selected is hardcode-defined, it will be overriden by v-model
export default {
data() {
return {
priority: "High",
priorities: ["High", "Medium", "Low"]
}
}<select
id="priority"
v-model="priority"
class="form-control">
<option v-for="priority in priorites">{{ priority }}</option>
</select>v-model does 2 things behind the schene:
v-bind:value="data.name"displays the value in realtime.@input="data.name=$event.taget.value"change event, updates the data on change of input.
Using submit button will normally trigger a request to a server. To prevent this, and async'ly fire a submit use:
@click.prevent="submit"
methods: {
submit() {
console.log('submit!');
}
}Switching routes from Vue SPA.
npm install vue-router --save
To set up the routes, we need to:
-
Initialize vue router on
main.tsimport VueRouter from 'vue-router'; Vue.use(VueRouter);
-
Define your basic routes, put them sin separate file:
import Home './..'; export const routes = [ { path: '/', component: Home }, // ... more routes ]
-
Use your routes in
main.tsmain vue instance.import { routes } from './routes'; Vue.use(VueRouter); const router = new VueRouter({ routes }); new Vue({ el: '#app', router, render: h => h(App) })
-
We need to tell where to place/load the routes.
<!-- built in components this where the routed components goes into --> <router-view></router-view>
localhost:/#/
The route after the hash tag handled by the javascript app to do routes other than submitting to server w/c we don't do on SPAs.
Using withoug hashtag style on routing #. We can history mode w/c can be setup like this:
const router = new VueRouter({
routes,
mode: 'history' // the default mode is hash
});Using the built-in component <router-link> to navigate to page instead of normal <a> tag.
The router-link do not re-render each page as the normal a href would do.
<router-link to="/user">User</router-link>We need to know what page are we on. By removing the parent li add override/use its class attribute [active].
It looks complicated honestly:
<router-link to="/user" tag="li" active-class="active" exact><a>User</a></router-link>The exact attribute is used to identify exactly the curren t active route to set a class as active.
In a vue instance we can access the $router property. We can trigger the navigation by calling push.
this.$router.push('/'); // simple call
this.$router.push({ path: '/' }); // we could also pass an objectPassing a parameter that can be use by the component.
{ path: '/user/:id', component: User }To fetch the parameter dynamically from the route
this.$route.params.id // for route `/user/:id`When the same route changes but have different parameter. The next param will not be fetched because Vue resource use the same component resource. So to fix this issue we will apply a watch here.
Scenario:
/user/1
/user/2
watch: {
"$route"(to, from) {
// to is just a route -- new route that is loaded
this.id = to.params.id
}
}Another way is to pass the parameter as prop to the target component:
{ path: '/hello/:name', component: Hello, props: true }, // Pass route.params to propsChild routes can be nested using children property
{ path: '/user', component: User, children: [
{ path: '', component: UserStart }, // root for /user
{ path: '/:id', component: UserDetail },
{ path: '/:id/edit', component: UserEdit }
] }In this case we will be adding new <router-view> again on the User component, for the child route's component.
The same with navigating routes normally using <router-link>
<router-link to="/user/1" tag="li">User</router-link>
<!-- tag="li" attribute as `<li> ... <li>` -->On the router link to attribute use bind so we can insert a js code, we can concatenate the param to construct the actual URL.
<router-link tag="li" active-class="active" :to="'/user/' + $route.params.id + /edit">User</router-link>Routes can be named:
{ path: '/:id/edit', component: UserEdit, name: 'userEdit' }And in the <router-link> we can change the concatenation simply by:
<router-link
tag="li"
:to="{ name: 'userEdit', params: { id: $route.params.id } }">
User Edit
</router-link>Adding names to routes can be use on navigating routes thru code also; much simplier and more clean.
this.$route.push({ name: 'home' }); // can also pass params etc..Adding parameter in URL like ?a=b. This can be added in the route object.
this.$route.push({ name: 'home', query: { locale: 'en' } });To access this on the code:
this.$route.query.localeOther than naming routes, we can also name route views like slots.
export const routes = [
{ path: '/', components: {
default: Home, // default for route '/'
// the keys here relates to the `name` attribute in target component's <router-view>
header: Header,
footer: Footer
} },
]In the Home component"
<route-view name="header"><route-view>
<!-- Unnamed w/c will be the target of the default component -->
<route-view><route-view>
<route-view name="footer"><route-view>Cases like route is not covered by our routes, we need to redirect it to specific path.
{ path: '/redirect-me', redirect: '/user' }Once route is visited, automacally redirect to differen to route.
Catch everything w/c is not handled by other of our routes
{ path: '*', redirect: '/user' }* is the wildcard character w/c catches all routes that did not handled by our routes.
How to animate by switching different routes. We can simply wrap the router view by transitions. Checkout other animation styles and modes
<transistion name="slide" mode="out-in">
<router-view></router-view>
</transistion># default browser behaviour jumps to section of a page with a ref like id="data" with url hash #data
To add the hash thru routing we can:
{ name: 'userEdit', params: { id: $route.params.id }, hash: '#data' }
// now we can pass the hash but it doesn't jump to section yet like the browser's normal behaviousIn the route config we can use scrollBehavior prop for control:
const router = new VueRouter({
routes,
mode: 'history',
scrollBehaviour(to, from, savedPos) {
if (savedPos) {
return savedPos; // return user po where he was at, switching back to prev route for example
}
if (to.hash) {
return { selector: to.hash }; // automaticall scrolls to target section (using coordinates)
}
return { x: 0, y: 0 }; // setting the default pos, when accessing regukar routes
}
});Control the user if he is allowed to enter or leave the route.
Basically we can add beforeEach in the router variable w/c exexcuted on every route actions.
const router = new VueRouter({ .. });
router.beforeEach((to, from, next) => {
// be aware that this is executed everytime.
// some kind of generic, global checks here
next() // pass nothing add continue the route.
next(false) // it will abort
next({ ... }) // or with params to redirect.
});So to protect certain routes, we can setup beforeEnter like:
{
path: '/dashboard',
component: Dashboard,
name: 'dashboard',
beforeEach: (to, from, next) { // so this guard is only available on this route
// same setup from the global before each :)
}
}Another way is to imlement / use a Vue Router hook on the target component:
beforeRouteEnter(to, from, next) {
// as long as you dont call next() here the component instance will not initialize.
/** If you access the instance here, it will be undefined.
* to do this, next() provided a callback for us to access the component instance after it is loaded:
*
* next(vm => { vm.link; })
*/
next(); // same rule from the route setup
}We can also use the beforeLeave hook on the component:
beforeRouteLeave(to, from, next) {
// do logic here
next(); // navigate away
}We only load part of the application when we only need it. Saves performance for bigger application. Lazy loading can be achieved by using webpack.
Laze loaded components are bundled separately. If route is visited it download that bundle.
// syntax w/c webpack recognizes
const User = resolve => {
require.ensure(['./components/User.vue'], () -> {
resolve(require('./components/User.vue')) // webpack will load this when route is visited
});
}
const routes = [
{ path: '/user', component: User }
]Managing state for bigger application using VueX
Local state management issue like passing data between children, might not good for bigger applications. Using event bus is a way, but it can be pretty crowded. On a bus, changes is very hard to track.
The idea is to store in a central place that will hold a state. With this can be change/get data from different components.
Create a store folder under your src folder
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
Vuex.store({
state: { // not arbitrary name, need it for vuex.
counter: 0;
}
});Set it up on vue instance
// main.ts
import { store } from './store/store';
// same with route add it on vue instance config
new Vue({
el: '#app',
store, // store: store
render: h => h(app)
});Access the store from a component
// $store property is provided from Vue.use(Vuex)
// this gives us the access to the central store
this.$store.state.counter++
// and in the displaying the data from another component, use `computed`
computed: {
counter() {
return this.$store.state.counter;
}
}The above solutions still has flaws. Using the a getter (below) can be an eysore. Eventually you will be repeating the code all over the components and does not look good.
this.$store.state.counter;There is a better way to solve this issue:
Instead directly accessing state, we can create a Getter
This would get the state from the store, performs calculation it should perform.
Vuex.store({
state: { // not arbitrary name, need it for vuex.
counter: 0;
},
getters: {
doubleCounter: state => {
state.counter * 2; // computed getter of state
}
}
});
// To access it from the component
this.$store.getters.doubleCounter;A vuex helper to set getters directly as computed data.
import { mapGetters } from 'vuex';
// ...
computed: mapGetters([
'doubleCounter',
'stringCounter'
]);
// Can also pass object
computed: mapGetters({
anotherPropName: 'doubleCounter'
});Using ES6 spread operator, we can still add our own computed properties:
computed: {
...mapGetters([
'doubleCounter',
'stringCounter'
]),
ourOwnComputedProp() {
// ...
}
};If we have getters we also have setters, b ut we call it Mutations. because it updates / changes the state.
This mutations are commited that will update a state.
--> COMMIT --> (MUTATIONS) --> CHANGE STATE (STORE)
In the store setup, we can add a mutations property.
Vuex.store({
state: { // not arbitrary name, need it for vuex.
counter: 0;
},
// ...
mutations: {
increment: state => {
state.counter++;
}
}
});
// To use / commit this use:
this.$store.commit('increment');To automatically add this to our methods, we can use mapMutations
import { mapGetters } from 'vuex';
//...
methods: {
...mapMutations([
'increment',
'decrement'
]),
ourOwnMethods() {
// ...
}
};
// this.increment
// @click="increment"This is to track mutations, adjustments of your state orderly.
If we need to add asynchrounous task, we have to add an actions layer between component and mutations.
component ---> dispatch() ---> ACTIONS ---> commit() ---> MUTATIONS ---> change state ---> STORE
In the store setup, we can add a property actions. Actions are like methods.
Vuex.store({
state: { // not arbitrary name, need it for vuex.
counter: 0;
},
// ...
actions: {
// parameter is a context object w/c you have an access to:
increment: ({ commit, dispatch, getters }) => {
commit('increment');
},
asyncIncrement: ({ commit }) => {
setTimeout(() => {
commit('increment');
// commit('increment', data) second argument of mutation is the payload / data here
}, 2e3);
}
}
});
// To use the actions directly
this.$store.dispatch('increment')
// this.$store.dispatch('increment', payload) pass payload as second argumentimport { mapActions } from 'vuex';
//...
methods: {
...mapActions([
'increment',
'decrement'
]),
ourOwnMethods() {
// ...
}
};
// this.increment
// @click="increment"IMPORTANT NOTE: You can't pass second argument to actions / mutations, dispatch only accepts 1 arg. Pass an object with many properties you want to use instead.
We cannot usev-bind to get and update state value, instead we can do the manual way;
By setting :value and @input to the input and setup computed and methods for this:
<input type="text" :value="value" @click="updateValue" />//...
computed: {
value() {
return this.$store.getters('value');
}
},
methods: {
updateValue(event) {
this.$store.dispatch('updateValue', event.target.value);
}
}There are another exception / rare way, if we want to bind the value.
By using get and set methods of computed data.
//...
computed: {
value: {
get() {
return this.$store.getters('value');
},
set(value) {
this.$store.dispatch('updateValue', value);
}
}
},So then in HTML
<input :value="value" />Improving our store by adding modules folder. The idea of this, certain parts may have belong to different part of the appliction.
├── store
│ ├── modules # States grouped together as modules
│ │ ├── counter.js # Some specific feature-specific components
│ │ └── value.js # shared components across all codes
│ └── store.js # main store
Inside the couter file, we can setup the state, getters, mutatations and actions similarly.
// .. counter.js
const state = {
counter: 0
}
const getters = {
doubleCounter: state => {
return state.counter * 2;
}
}
const mutations = {
increment(state, payload) => {
state.counter += payload;
}
}
const actions = {
increment({ commit }, payload) {
commit('increment', payload); // string is the mutation name
}
}
export {
state,
getters,
mutations,
actions
}To use the this module in the main store we will setup a module collector for that. Here, we will use the module property on store setup.
import counter from './modules/counter';
Vuex.store({
state: { // not arbitrary name, need it for vuex.
counter: 0;
},
// ...
modules: {
counter
}
});Some actions, mutations, etc. remains in the main store setup, like shareable data for example. With that, the store file will grow bigger. We can separate the actions etc. to separate files.
├── store
│ ├── modules # States grouped together as modules
│ │ ├── counter.js # Some specific feature-specific components
│ │ └── value.js # shared components across all codes
│ ├── actions.js # Some specific feature-specific components
│ └── store.js # main store
actions.js
export const updateValue = ({ commit }, payload) => {
commit('');
}And in the store.js
import * as actions from './actions';
Vuex.store({
state: { // not arbitrary name, need it for vuex.
counter: 0;
},
actions,
mutations,
getters,
modules: {
// ..
}
});This makes the folder structure very clean and manageable!
Case like different modules have the same method names. This can cause [vuex] duplicate key error error.
All methods specifically per actions etc. should be unique because we gathered it in single store.
Another pattern we can use especially for big applications: We will provide action types for this, this assigns a unique name for the methods:
// types.js
export const DOUBLE_COUNTER = 'counter/DOUBLE_COUNTER'; // prefixed with the module name!
// counter.js
import * from types from '../types';
export const getters = {
[types.DOUBLE_COUNTER]: state => {
return state.counter
}
}And in the component we can access this (for example a getter)
We can still use the mapGetters by assigning a different name.
import * as types from '../types';
computed: {
...mapGetters({
doubleCounter: types.DOUBLE_COUNTER
})
}Check it here


