Class binding

Let's us bind data to manipulate an element's class list

Could be solved with v-bind

<div :class="classes"></div>

//...
computed: {
  classes() {
    let classes = ''

    if(this.isActive) {
      classes += 'active '
    }

    if(this.hasError) {
      classes += 'text-error'
    }

    return classes
  }
}

Vue provides special enhancements when v-bind is used with class attributes

<div :class="{ active: isActive }"></div>

CSS Class

Expression

Object Syntax

<div class="active"></div>

Output

<div :class="{ active: isActive, 'text-red': hasError }"></div>

Multiple classes

data() {
  return {
    active: true,
    hasError: true
  }
}
<div class="active text-red"></div>

Template

Data

Output

Class bindings are reactive

Extract complex class lists

<div class="classObject"></div>
data() {
  return {
    active: true,
    hasError: false,
    error: {}
  }
},
computed: {
  classObject() {
    return {
      active: this.isActive && !this.hasError,
      'text-danger': this.hasError && this.error.type === 'fatal'
    }
  }
}

Array Syntax

<div :class="['active', 'text-danger']"></div>

Mix Array and Object Syntax

<div :class="[{ active: true }, { 'text-danger': false }]"></div>

Array Syntax

<div :class="[isActive ? activeClass : '', errorClass]"></div>

Applied when isActive is truthy

Style binding

💅

Same syntax as with class binding

Array and Object Syntax

<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

Style binding example

<div :style="styleObject"></div>
computed: {
    styleObject: {
      color: this.font.color,
      fontSize: this.font.size + 'px'
    }  
}

E.g. to computed or data

Styles can also be extracted

Questions?

Assignment # 6

👩‍💻👨🏽‍💻

⏰ 25 mins

Lunch Break

🍔

⏰ 30 mins

Components

So far, we worked with a single Vue app instance

So far, we worked with a single Vue app instance

Vue.createApp({
  data() {},
  computed: {},
  methods: {}
}).mount('#app')

Big Applications

Single Instance is not enough

Well organised Vue applications

Components

===

Components

name

+

Vue.createApp({
  data() {},
  computed: {},
  methods: {}
}).mount('#app')

Like little Vue application instances with a name

Components

// CounterButton.vue
<script>
export default {
  data() {},
  computed: {},
  methods: {}
}
</script>

Like little Vue application instances with a name

Single File Component

(SFC)

Single File Components

// CounterButton.vue
<script>
export default {
  // the logic for your component
}
</script>

<template>
<!-- The HTML for your component -->
</template>

<style>
/* The styles for your component */
</style>

A special Vue file format optimized for writing components

will require additional setup (more on this in a few)

Single File Components

// CounterButton.vue
<script>
export default {
  data() {
    return {
      count: 0,
    };
  },
};
</script>

<template>
<button @click="count++">{{ count }}</button>
</template>

CounterButton.vue Example

Single File Components

// CounterButton.vue
<script>
export default {
  data() {
    return {
      count: 0,
    };
  },
};
</script>

<template>
<button @click="count++">{{ count }}</button>
</template>

CounterButton.vue Example

// App.vue
<script>
import CounterButton from "@/CounterButton.vue"
</script>

<template>
<CounterButton></CounterButton>
</template>

Single File Components

CounterButton.vue Example

Components

are like

custom HTML elements

Components

are reusable

// App.vue
<script>
import CounterButton from "@/CounterButton.vue"
export default{
  components: { CounterButton }
}
</script>

<template>
<CounterButton></CounterButton>
<CounterButton></CounterButton>
<CounterButton></CounterButton>
<CounterButton></CounterButton>
</template>

Components

are reusable

Components

allow us to break our applications into small pieces and organise it

Components

can be big - like a complex form component

can be small - like a toggle switch

Components are one of the most powerful features of Vue.js

 They allow you to extend basic HTML elements to encapsulate reusable code.

It's possible to define components without Single File Components

but it's very uncommon and not nearly as developer friendly

How to Support Single File Components

  • The browser doesn't know what a .vue file is
  • It knows CSS, HTML, JavaScript but not .vue
  • We need a way to transform (or COMPILE) .vue files to regular HTML, CSS, and JavaScript

Vite is a build tool that can compile .vue files (among other things)

How to Start a Vue Project with Vite

npm init vue@3

Demo Time!

SFC Support for VS Code

How to Start a Vue Project with Vite

(in a StackBlitz Playground)

https://vite.new/vue

Demo Time!

This is how you'll build most of your applications

but everything we've discussed so far works the same in this context

Questions?

Assignment # 7

👩‍💻👨🏽‍💻

⏰ 25 mins

Communication

between

Components

Components are not aware of their surroundings

Product Card

// ProductCard.vue
<template>
<div>
  <a :href="link">
    <img :src="image" :alt="title">
  </a>
  <p>{{ title }}</p>
  <p>{{ price }} kr</p>
</div>
</template>

What title? What price?

Product Card

in order to know the product details we need a way to pass it the data

we do that using an option, called props

Component Props

props allow us to pass data to a component

// ProductCard.vue
<script>
export default{
  props: ['title', 'price', 'link', 'image']
}
</script>

<template>
<div>
  <a :href="link">
    <img :src="image" :alt="title">
  </a>
  <p>{{ title }}</p>
  <p>{{ price }} kr</p>
</div>
</template>

Component Props

props are used the same way

as data and computed

// ProductCard.vue
<script>
export default{
  props: ['title', 'price', 'link', 'image']
}
</script>

<template>
<div>
  <a :href="link">
    <img :src="image" :alt="title">
  </a>
  <p>{{ title }}</p>
  <p>{{ price }} kr</p>
</div>
</template>

Component Props

are given values as HTML attributes

// App.vue
<script>
import ProductCard from "@/components/ProductCard.vue"
export default {
  components: ProductCard,
  data() {
    return {
      items: [
        { title: 'Socks', price: 3, link: 'comfysocks.com', image: 'socks.jpg' },
        { title: 'Shoes', price: 118, link: 'comfyshoes.com', image: 'shoes.jpg' },
        { title: 'Shirt', price: 19, link: 'comfyshirts.com', image: 'shirt.jpg' }
      ]
    }
  }
}
</script>
<template>
  
<ProductCard 
  v-for="item in items"
  :title="item.title"
  :price="item.price"
  :link="item.link"
  :image="item.image"
></ProductCard>

</template>

Parent → Child

communication

Parent → Child

communication

pass data from top to bottom

// ProductCard.vue
<script>
import ProductCardImage from "@/components/ProductCardImage.vue"
export default{
  components: ProductCardImage
  //...
}
</script>

<template>
  <div>
    <ProductCardImage :image="image"></ProductCardImage>
    <p>{{ title }}</p>
    <p>{{ price }} kr</p>
  </div>
</template>

Components can use other components

Props flow down

From Vuejs.org

Props are reactive

if the parent data changes, it will flow down and automatically update the nested components

Component props examples

<div id="app">
  <UserCard :user="user"></UserCard>
  <NotificationMessage type="success"></NotificationMessage>
  <NavbarItem text="home" url="/"></NavbarItem>
</div>

Component props tips

Components should not change the value of props

// ProductCard.vue
<script>
export default{
  props: ['title', 'price', 'link', 'image'],
  methods:{
    doThing(){
    	this.title = "something else" // ❌ Big no, no!
    }
  }
}
</script>

<template>
...
</template>

Use object syntax to validate props

app.component('product-card', {
  template: '#product-card-template',
  props: {
    title: {
      type: String,
      required: true
    },
    price: {
      type: Number
    }
  }
})

Support props types

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

You can create custom prop validation rules 💪

for example validUrl

Questions?

Assignment # 8

👩‍💻👨🏽‍💻

⏰ 20 mins

Child -> Parent Communication

Communication takes place via events

Child component emits an event

Parent component catches the event and takes action

Child component emits an event

// ClickCounter.vue
<script>
export default{
  data () {
    return {
      count: 0
    }
  },
  methods: {
    increase () {
      this.count++
      this.$emit('increased', this.count)
    }
  }
}
</script>

<template>
  <button v-on:click="count++">hit me {{ count }}</button>
</template>

event name

payload

Parent listens for the event

// App.vue

<script>
export default{
   data() {
      return {
        counter: null
      }
    },
    methods: {
      setCounter (count) {
        this.counter = count
      }
    }
}
</script>

<template>
	<ClickCounter @increased="setCounter"></ClickCounter>
</template>

event name

event handler

event payload

Parent listens for the event

// App.vue

<script>
export default{
   data() {
      return {
        counter: null
      }
    },
}
</script>

<template>
	<ClickCounter @increased="counter = $event"></ClickCounter>
</template>

inline handler

event payload

Custom Events do not propagate

Like ordinary HTML events do

Custom Events do not propagate

<ProductCardImage>
<ProductCard>
<ProductList>

Custom Events do not propagate

<ProductCardImage>
<ProductCard>
<ProductList>

$emit('image-broken')

Custom Events do not propagate

<ProductCardImage>
<ProductCard>
<ProductList>

$emit('image-broken')

$emit('image-broken')

You can catch and emit upwards

You can catch and emit upwards

Just don't do it too much!
(Might need Pinia for global state management)

💡Data goes to child via props

💡Data goes to parent via events

Component Communication Recap

💡Data goes to child via props

💡Data goes to parent via events

Component Communication Recap

If you need to do that for many levels use a state management pattern

emits option

🆕

// ClickCounter.vue
<script>
  export default{
    emits: ['increased'],
    data () {
      return {
        count: 0
      }
    },
    methods: {
      increase () {
        this.count++
        this.$emit('increased', this.count)
      }
    }
  }
</script>

<template> ....</template>

Declare emitted events

app.component('click-counter', {
  template: '<button v-on:click="increased">hit me {{ count }}</button>',
  emits: ['increased'],
  props: ['initialCount'],
  data () {
    return {
      count: this.initialCount ?? 0
    }
  },
  methods: {
    increase () {
      this.count++
      this.$emit('increased', this.count)
    }
  }
})

Syntax like props

export default {
  emits: {
    // no validation
    click: null,

    // with validation
    submit: payload => {
      if (payload.email && payload.message) {
        return true
      } else {
        console.warn(`Invalid submit event payload!`)
        return false
      }
    }
  }
}

Object syntax

Highly recommended to document emitted events

  • Great for documentation
  • Prevents edge-case issues when re-emitting native events such as click
  • Gives info to your IDE to better autocomplete events

Vue DevTools 🤟

Events are logged in devtools

Questions?

Assignment # 9

👩‍💻👨🏽‍💻

⏰ 20 mins

Lifecycle Hooks

Each vue instance goes through a set of initialisation steps

In each step, you can run code ⚡️

using lifecycle hooks

Lifecycle hooks

  • created - instance is ready - not mounted yet
  • mounted - instance is mounted to the DOM
  • unmounted - instance is removed from the DOM
{
  props: ['id'],
  data () {
    return {
      blogPost: null
    }
  },
  created () {
    axios.get('api/posts/' + this.id).then(response => {
      this.blogPost = response.data
    })
  }
}

Lifecycle hooks

created - often used to fetch data from API

Example

Lifecycle hooks

created - fetch data from an API while component is added to the DOM

mounted - define global event listeners

beforeUnmounted - remove any event listeners we have defined globally

Use Cases

Lifecycle hooks

https://vuejs.org/guide/essentials/lifecycle.html

Questions?

Assignment # 10

👩‍💻👨🏽‍💻

⏰ 20 mins

2. Vue 3 Fundamentals Workshop - Components (⚠️Options API)

By Daniel Kelly

2. Vue 3 Fundamentals Workshop - Components (⚠️Options API)

  • 570