INTRODUCTION TO VUE

LET'S START WITH A DEMO

OUTLINE

  1. Introduction

  2. DOM Interactions

  3. Components

  4. Computed properties

  5. Handling Events

  6. The Vue Instance

  7. Vue CLI

  8. Vue Router

  9. Handling Forms

  10. Directives and Filters

INTRODUCTION

Introduction

Over the last 10 years, web pages became more and more powerful thanks to JavaScript 💪

JavaScript code moved from server side to client side (browser)

Introduction

= POOR JS CODE 😓

Introduction

  • Approchable

  • Versatile

  • Performant

  • Maintanable

  • Testable

Introduction

Introduction

Component based development

<body>
  <tweets>
    <tweet text="This is my 1st tweet!"><tweet>
    <tweet text="This is my 2nd tweet!"><tweet>
    <tweet text="This is my 3rd tweet!"><tweet>
  </tweets>
</body>

Component based development

<!-- tweet component -->

<div class="tweet-container">
  <div class="username">@johndoe</div>
  <div class="tweet-text">This is my 1st tweet!</div>
</div>

Introduction

Component based development

A component is an encapsulated set of behaviours / features / logics

A component has a defined interface that allows creation / reusability

Introduction

Component based development

ALL major JS frameworks use COMPONENTS

Introduction

DOM INTERACTIONS

DOM Interactions

Text rendering

<div id="app">
  <span>Hello {{ name }}!</span>
</div>
new Vue({
  el: '#app',
  data() {
    return {
      name: 'John Doe'
    }
  }
})

DOM Interactions

Text rendering

<div id="app">
  <span>Hello {{ name }}!</span>
</div>

when name value changes, the HTML is updated

data object is reactive

DOM Interactions

Text rendering

<span>{{ const name = 'John Doe' }}</span>

A binding ({{ ... }}) can only contain 1 single expression

⚠️ Not working! This is a statement, not an expression

DOM Interactions

Attributes

<button v-bind:disabled="buttonDisabled">Click me</button>
<!-- or -->
<button :disabled="buttonDisabled">Click me</button>
// ...
data() {
  return {
    buttonDisabled: true
  }
}

DOM Interactions

Attributes

<a :href="url">Google</a>
// ...
data() {
  return {
    url: 'https://www.google.com'
  }
}

DOM Interactions

Class binding

<div :class="{ 'is-active': isActive }"></div>
// ...
data() {
  return {
    isActive: true
  }
}

is-active will be added to the div if isActive is truthy

DOM Interactions

Style binding

<div :style="{ width: width, backgroundColor: red }"></div>
// ...
data() {
  return {
    width: '100px',
    red: '#FF0000'
  }
}

DOM Interactions

Conditional rendering

<div v-if="quantity === 0">Nothing</div>
<div v-else-if="quantity > 5">Some</div>
<div v-else-if="quantity > 10">Some more</div>
<div v-else>Many</div>

v-else-if must follow v-if directive

v-else must follow v-if or v-else-if directive

DOM Interactions

Conditional rendering

<div v-show="isVisible">Hey there!</div>

v-show will always render the element unlike v-if

(v-show only toggles display css property)

DOM Interactions

List rendering

<ul>
  <li v-for="name in names">{{ name }}</li>
</ul>
// ...
data() {
  return {
    names: ['John', 'Jane', 'Alice']
  }
}

v-for ... of also works (closer to JavaScript standard)

DOM Interactions

List rendering

<ul>
  <li v-for="(name, index) in names">
    {{ index }} - {{ name }}
  </li>
</ul>
// ...
data() {
  return {
    names: ['John', 'Jane', 'Alice']
  }
}

DOM Interactions

List rendering

<ul>
  <li v-for="(prop, key) in user">
    {{ key }}: {{ prop }}
  </li>
</ul>
// ...
data() {
  return {
    user: {
      name: 'John',
      age: 22,
      state: 'CA, California'
    }
  }
}

COMPONENTS

Components

The most powerful feature of Vue

Components

Global registration

// Register before root Vue instance
Vue.component('hello-world', {
  template: '<div>Hello, World!</div>'
})

new Vue({
  el: '#app'
})
<div id="app">
  <hello-world></hello-world>
</div>

Components

Local registration

const HelloWorld = {
  template: '<div>Hello, World!</div>'
}

new Vue({
  // ...
  components: {
    // Only available in parent's template
    'hello-world': HelloWorld
  }
})

Components

.vue files

⚠️ Available with Webpack

and vue-loader

Components

.vue files

Components

Communication

Components

Props

Every component instance has its own isolated scope

A prop is a custom template attribute

<template>
  <span>{{ message }}</span>
</template>

<script>
export default {
  props: ['message']
})
</script>

HelloWorld.vue

Components

Static props

<!-- If registered as HelloWorld component -->

<HelloWorld message="Hello, World!"></HelloWorld>
Hello, World!

⚠️ When using non-string templates, camelCased prop names need to use their kebab-case equivalents

(does not apply to .vue files)

Components

Dynamic props

helloMessage is dynamic and comes from HelloWorld's parent component

<HelloWorld v-bind:message="helloMessage"></HelloWorld>
<!-- or -->
<HelloWorld :message="helloMessage"></HelloWorld>

Components

Props validation

// ...
props: {
  propA: Number,
  propB: [String, Number],
  propC: {
    type: String,
    required: true
  },
  propD: {
    type: Number,
    default: 100
  }
}

A prop type can be:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array
  • Symbol

Components

slots

Sometimes, components needs to be composed this way

<Header />
<Checkout>
  <Item name="product1"></Item>
  <Item name="product2"></Item>
  <Item name="product3"></Item>
</Checkout>

Content distribution: includes the parent “content” inside the component’s own template

Components

slots

<!-- Checkout component template -->
<div>
  <h2>Checkout</h2>
  <slot>
    <!-- only visible if no content to be distributed -->
    <span>There's no item in your cart</span>
  </slot>
</div>
<div>
  <h2>Checkout</h2>
  <span>product1</span>
  <span>product2</span>
  <span>product3</span>
</div>
<Checkout>
  <Item name="product1"></Item>
  <Item name="product2"></Item>
  <Item name="product3"></Item>
</Checkout>

COMPUTED PROPERTIES

Computed properties

<div>
  {{ message.split('').reverse().join('') }}
</div>

Sometimes, you need to transform data for display

In-template expressions are convenient but this 👆 is bad

👉 USE COMPUTED PROPERTIES FOR COMPLEX LOGIC

Computed properties

<div>
  <span>{{ message }}</span>
  <span>{{ reversedMessage }}</span>
</div>

A computed property is a reactive function that returns data in another form

// ...
data() {
  return { message: 'Hello, World!' }
},
computed: {
  reversedMessage() {
    return this.message.split('').reverse().join('')
  }
}
Hello, World!

!dlroW ,olleH

Computed properties

<div>
  <span>{{ message }}</span>
  <span>{{ reversedMessage }}</span>
</div>

The value of reversedMessage will always be dependent of message

👇

Hello, World!

!dlroW ,olleH

Every time message changes, reversedMessage is updated

Computed properties

this.reversedMessage // Ok -> !dlroW ,olleH
this.reversedMessage() // Error

A computed property is a getter function (can't pass arguments)

Computed properties

<span>{{ greetings() }}</span>

It is possible to call a method in a template

// ...
data() {
  return { name: 'John Doe' }
},
methods: {
  greetings() {
    return `Hello, ${this.name}!`
  }
}

Computed properties

<span>{{ greetings }}</span>

But here 👇, better use a computed property

// ...
data() {
  return { name: 'John Doe' }
},
computed: {
  greetings() {
    return `Hello, ${this.name}!`
  }
}

Computed properties

A computed property is cached based on its dependencies

A method will be called on every re-render phase

WHEREAS

HANDLING EVENTS

Handling events

<span>{{ counter }}</span>
<button v-on:click="add">Add 1</button>
<!-- or -->
<button @click="add">Add 1</button>

Listening to DOM events

// ...
data() {
  return { counter: 0 }
},
methods: {
  add() {
    this.counter++
  }
}

Handling events

<button @click="hello('John')">Hello</button>

Listening to DOM events

// ...
methods: {
  hello(name) {
    console.log(`Hello ${name}`)
  }
}

Event's listeners (= methods) can have arguments

Handling events

<button @click="intercept('hello', $event)">
  Intercept me!
</button>

Listening to DOM events

// ...
methods: {
  intercept(msg, $event) {
    console.log(msg)
    $event.preventDefault()
  }
}

$event gives access to the native DOM event

Handling events

<!-- Child component template -->
<button @click="add">Add 1</button>

Communication from child to parent component

// Child component instance
// ...
methods: {
  add() {
    this.$emit('addNumber', 1)
  }
}

Child component emits a custom event

Handling events

<!-- Parent component template -->
<Child @addNumber="add"></Child>

Communication from child to parent component

// Parent component instance
// ...
methods: {
  add(value) { // Here value is 1
    this.counter += value
  }
}

Parent component listens to child custom event

THE VUE INSTANCE

The Vue Instance

import Vue from 'Vue'
import App from './App'

// vm: ViewModel
const vm = new Vue({
  el: '#app',
  template: '<App />',
  components: { App },
  // other options
})

Creating a Vue Instance

The Vue Instance

Root Instance

App

Header

Week

Movie

TimeFilter

GenreFilter

CountryFilter

Movie

Movie

Ticket

Ticket

The Vue Instance

Reactive Data

const vm = new Vue({
  data() {
    return { one: 1 }
  }
})
vm.two = 2 // will not render a view update!

⚠️ Properties in data are only reactive if they existed when the instance was created

The Vue Instance

$watch

const vm = new Vue({
  data() {
    return { one: 1 }
  },
  watch: {
    one(newVal, oldVal) {
      // do something
    }
  }
})
vm.one = 2 // will trigger watch function

The Vue Instance

$watch (immediate)

const vm = new Vue({
  // ...
  watch: {
    one: {
      immediate: true,
      handler(newVal, oldVal) {
        /* will execute immediately
        and on each update */
      }
    }
  }
})

The Vue Instance

Instance Lifecycle Hooks

The Vue Instance

Instance Lifecycle Hooks

The Vue Instance

Instance Lifecycle Hooks

The Vue Instance

Instance Lifecycle Hooks

const vm = new Vue({
  // ...
  beforeCreate()  { ... },
  created()       { ... },
  beforeMount()   { ... },
  mounted()       { ... },
  beforeUpdate()  { ... },
  updated()       { ... },
  beforeDestroy() { ... },
  destroyed()     { ... }
})

VUE CLI

Vue CLI

# install vue-cli
npm install -g @vue/cli

# create a new project with command line
vue create hello-vue

# create a new project with web UI
vue ui

The official tool to quickly scaffold Vue SPA

Vue CLI

Install default presets 

Vue CLI

Or install all features you need 👌

Vue CLI / UI

Vue CLI / UI

Vue CLI / UI

Vue CLI / UI

Vue CLI

Static Assets Handling

Static assets can be handled by webpack (like source code)

import Vue from 'vue'
import App from './App'
import '@/assets/css/style.css'
import { movies } from '@/assets/json/movies.json'
<img src="require('@/assets/img/image.jpg')">

Vue CLI

Static Assets Handling

Or they can be placed in public folder

data () {
  return {
    // configured in vue.config.js
    baseUrl: process.env.BASE_URL
  }
}
<img :src="`${baseUrl}image.png`">

Vue CLI

Environment Variables and Modes

.env              # loaded in all cases
.env.local        # loaded in all cases, ignored by git
.env.[mode]       # only loaded in specified mode
.env.[mode].local # only loaded in specified mode, ignored by git

These files must be place in the project root

  • development: used by vue-cli-service serve

  • production: used by vue-cli-service build

  • test: used by vue-cli-service test

Vue CLI

Environment Variables and Modes

# .env.development
VUE_APP_DEBUG_MODE=true
# .env.production
VUE_APP_DEBUG_MODE=false
// Application code
console.log(process.env.VUE_APP_DEBUG_MODE);

// true when `npm run serve`
// false when `npm run build`

VUE ROUTER

Vue Router

What's a Router?

to display different "pages" (views)

Navigate to different URLs

without reloading application

Vue Router

What's a Router?

HEADER

MENU

/foo

/bar

/baz

Vue Router

Installation

npm install --save vue-router
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

Vue Router

Getting started

Map components to routes

import Router from 'vue-router'
import Foo from '@/components/Foo.vue'
import Bar from '@/components/Bar.vue'

const router = new Router({
  routes: [
    { name: 'foo', path: '/foo', component: Foo },
    { name: 'bar', path: '/bar', component: Bar }
  ]
})

new Vue({ el: '#app', router, /* ... */ })

Vue Router

Getting started

Specify where to render the components with

<template>
  <Header />
  <Menu />

  <!-- components will be dynamically
       rendered here according to the route -->
  <router-view />
</template>
<router-view />

Vue Router

Navigation with <router-link>

<template>
  <!-- <router-link> will be rendered as <a> -->
  <router-link to="/foo">Go to Foo</router-link>
  <router-link to="/bar">Go to Bar</router-link>
</template>

Navigation with $router

// ...
methods: {
  goToFoo() {
    this.$router.push('/foo')
  }
}

Vue Router

Dynamic Route Matching

// ...
routes: [
  // dynamic segments start with a colon
  { path: '/user/:id', component: User }
]
<!-- User component -->
<template>
  <div>Hello user {{ $route.params.id }}</div>
</template>
  • http://localhost:3000/user/1
  • http://localhost:3000/user/2

Vue Router

Nested Routes

routes: [
  { path: '/user/:id', component: User,
    children: [
      { path: 'profile', component: UserProfile },
      { path: 'posts', component: UserPosts }
    ]
  }
]
  • http://localhost:3000/user/1/profile
  • http://localhost:3000/user/1/posts

Vue Router

Nested Routes

<!-- App component -->
<template>
  <Header />
  <router-view />
</template>
<!-- User component -->
<template>
  Hello user {{ $route.params.id }}
  <!-- Here is rendered
       `UserProfile` / `UserPosts` component -->
  <router-view />
</template>

Vue Router

this.$router.push({ name: 'user', params: { id: 1 } })

// /foo?search=new
this.$router.push({ path: 'foo', query: { search: 'new' } })
<router-link :to="{ name: 'user', params: { id: 1 } }">
  User 1
</router-link>

Navigation with <router-link>

Navigation with $router

HANDLING FORMS

Handling Forms

Input bindings: text

<template>
  <form>
    <input v-model="fullName">
    <p>Fullname is {{ fullName }}</p>
  </form>
</template>

Two-way data-binding with v-model

(⚠️ v-model can only be used with input, textarea and select)

Handling Forms

Input bindings: text

<script>
data() {
  return {
    fullName: ''
  }
}
</script>

The initial value has to be declared inside the data function of the component

Handling Forms

Input bindings: text

<input
  :value="fullName"
  @input="fullName = $event.target.value">
<input v-model="fullName">

is syntax sugar for 👇

Handling Forms

Input bindings: checkbox

<input type="checkbox" v-model="checked">
<input type="checkbox" value="John" v-model="checkedNames">
<input type="checkbox" value="Jane" v-model="checkedNames">
<!-- checkedNames will be [] or ["John"] or ["Jane"]
     or ["John", "Jane"] -->

Multiple checkboxes (Array of value)

Single checkbox (boolean)

Handling Forms

Input bindings: radio

<input type="checkbox" value="John" v-model="name">
<input type="checkbox" value="Jane" v-model="name">
<input type="checkbox" value="Alice" v-model="name">
<!-- name will be "John" or "Jane" or "Alice" -->

Handling Forms

Input bindings: select

<select v-model="user">
  <option disabled value="">Select someone</option>
  <option>John</option>
  <option>Jane</option>
  <option>Alice</option>
</select>
<!-- user will be "John" or "Jane" or "Alice" -->

Handling Forms

Input bindings: select with dynamic options

<select v-model="selectedUser">
  <option v-for="user in users" :value="user.id">
    {{ user.name }}
  </option>
</select>
data() {
  return {
    selectedUser: 1,
    users: [
      { id: 1, name: 'John' },
      { id: 2, name: 'Jane' },
      { id: 3, name: 'Alice' }
    ]
  }
}

Handling Forms

Form validation

USE A PLUGIN

DIRECTIVES AND FILTERS

Directives and Filters

Custom directives

A directive overrides / enhances the behaviour of an element of the DOM

👉 Use only when you need to access / manipulate the DOM

Directives and Filters

Custom directives

// Register a global custom directive called `v-focus`
Vue.directive('focus', {
  inserted(el) {
    el.focus()
  }
})
<input v-focus>

Directives and Filters

Directive's hooks functions

bind
inserted
update
componentUpdate
unbind

Directives and Filters

Directive's hooks arguments

inserted(el, binding, /* ... */) {
  /*
   * - el: the element the directive is bound to
   * - binding: an object with the following properties:
   *   - name: the name of the directive without the `v-`
   *   - value: the value passed to the directive
   *   - oldValue: the previous value
   */
}

Directives and Filters

Directive's hooks arguments

<div v-color="#fff"></div>
bind(el, binding) {
  el.style.backgroundColor = binding.value
}

Example 1: set div background color

Directives and Filters

Directive's hooks arguments

<h1
  v-greetings="{ text: 'Hello, World!', color: '#fff' }">
</h1>
bind(el, binding) {
  el.textContent = binding.value.text
  el.style.backgroundColor = binding.value.color
}

Example 2: set h1 text and background color

Directives and Filters

Filters

A filter is a function that formats text

👉 Filters can be used in mustache interpolations and v-bind expressions

Directives and Filters

Filters

// Register a global filter called `uppercase`
Vue.filter('uppercase', function (value) {
  if (!value) return ''
  return value.toString().toUpperCase()
})
<!-- greetings = 'Hello, World!' -->
<h1>{{ greetings | uppercase }}</h1>
<!-- HELLO, WORLD! -->

Directives and Filters

Filters

<!-- filters can be chained -->
<h1>{{ greetings | filterA | filterB }}</h1>
<!-- filters can take arguments -->
<h1>{{ price | currency(2, '$') }}</h1>
Vue.filter('currency', function (price, format, symbol) {
  // ...
})

Introduction to Vue

By Nicolas Payot

Introduction to Vue

  • 1,282