- Intro to Vuejs
- Real World Vue
- App 1: Youtube Search Application
https://www.vuemastery.com/courses-path/beginner
https://www.vuemastery.com/courses/intro-to-vue-js/vue-instance
-
Install
Vuejs devtools
on chrome<div id="app"> {{ product }} </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data: { product: 'socks' } }) </script>
-
var app = new Vue({OPTIONS});
Creates new Vue instance -
OPTIONS: Store data and perform actions
-
{{ expression }}
: Used to produce or evaluate values. Examples:{{ firstName + lastName }}
combine values{{ clicked ? true : false }}
perform conditional logic{{ message.split('').reverse().join('') }}
run methods on our data
-
Vuejs is reactive, if we open our console and type:
app.product = "coat"
=> the value ofproduct
will change fromsocks
to `coat' in all html references
-
*v-bind
: Dynamically binds an attribute to an expression.
-
Shorthand:
:
-
<img v-bind:src="image">
src
: is the attributeimage
: is the expression
-
When using
v-bind
with attributes, there is a shorthand:
v-bind:src="image"
becomes:src="image"
:alt="description"
:href="url"
:title="toolTip"
:style="isStyled"
:class="isActive"
- ..etc
-
v-if:
<p v-if="inventory > 10">In stock</p> <p v-else="inventory <= 10 && inventory > 0">Almost sold out</p> <p v-else>Out of stock</p>
data: { inventory: 10 }
- Event listeners and child components inside the conditional block are properly destroyed and re-created during toggles
-
v-show:
<h1 v-show="showMsg">Hello!</h1>
much simpler - the element is always rendered regardless of initial condition, with CSS-based togglingGenerally speaking,
v-if
has higher toggle costs while v-show has higher initial render costs. So preferv-show
if you need to toggle something very often, and preferv-if
if the condition is unlikely to change at runtime.
-
v-for: Expects:
Array
|Object
|number
|string
|Iterable
(since 2.6)<ul> <li v-for="detail in details">{{ detail }}</li> </ul>
data: { product: `Socks`, image: `./assets/vmSocks-blue-onWhite.jpg`, inventory: 9, details: ["80% cotton", "20% polyster", "Gender-neutral"] }
-
The default behavior of
v-for
will try to patch the elements in-place without moving them. To force it to reorder elements, you need to provide an ordering hint with thekey
special attribute:<div v-for="variant in variants" :key="variant.variantId"> <p>{{ variant.variantColor }}</p> </div>
variants: [ { variantId: 2214, variantColor: "Green", }, { variantId: 2215, variantColor: "Blue", } ]
-
v-on:
- Shorthand:
@
- Expects:
Function
|Inline Statement
|Object
- Shorthand:
-
Example :
click
<button v-on:click="addToCart">Add to cart</button>
<div class="cart">
<p>Cart({{ cart }})</p>
</div>
methods: {
addToCart: function() {
this.cart += 1;
}
}
-
Example:
mouseover
<div v-for="variant in variants" :key="variant.variantId"> <p @mouseover="updateProduct(variant.variantImage)">{{ variant.variantColor }}</p> </div>
variants: [ { variantId: 2214, variantColor: "Green", variantImage: `./assets/vmSocks-green-onWhite.jpg` }, { variantId: 2215, variantColor: "Blue", variantImage: `./assets/vmSocks-blue-onWhite.jpg` } ],
-
The object syntax for
v-bind:style
is pretty straightforward - it looks almost likeCSS
, except it’s aJavaScript object
. You can use eithercamelCase
orkebab-case (use quotes with kebab-case)
for the CSS property names::style="{ 'background-color':variant.variantColor }"
:style="{ backgroundColor:variant.variantColor }"
-
It is often a good idea to bind to a style object directly so that the template is cleaner:
<div v-bind:style="styleObject"></div>
data: { styleObject: { color: 'red', fontSize: '13px' } }
-
Array syntax
: The array syntax forv-bind:style
allows you to apply multiple style objects to the same element:<div v-bind:style="[baseStyles, overridingStyles]"></div>
-
If you would like to also toggle a class in the list conditionally, you can do it with a ternary expression:
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
-
This will always apply
errorClass
, but will only applyactiveClass
whenisActive
is truthy.-
However, this can be a bit verbose if you have multiple conditional classes. That’s why it’s also possible to use the
object syntax
insidearray syntax
:<div v-bind:class="[{ active: isActive }, errorClass]"></div>
-
-
-
... Read more
-
Computed properties are cached until its dependency is changed
<h1>{{ title }}</h1>
computed: { title() { return this.brand + ' ' + this.product; } }
-
We update our code to make
inStock
andimage
computed properties. Now these properties depend on variant<div class="product-image"> <img v-bind:src="image" v-bind:alt="product"> </div> <p v-if="inStock > 10">In stock</p> <p v-else="inStock <= 10 && inStock > 0">Almost sold out</p> <p v-else>Out of stock</p> <div v-for="(variant, index) in variants" :key="variant.variantId" class="color-box" :style="{ 'background-color':variant.variantColor }" @mouseover="updateProduct(index)"> </div>
variants: [ { variantId: 2214, variantColor: "Green", variantImage: `./assets/vmSocks-green-onWhite.jpg`, variantQuantity: 9 }, { variantId: 2215, variantColor: "Blue", variantImage: `./assets/vmSocks-blue-onWhite.jpg`, variantQuantity: 0 } ], computed: { image: function() { return this.variants[this.selectedVariant].variantImage; }, inStock() { return this.variants[this.selectedVariant].variantQuantity; } }
-
Base example:
-
Define a new component called button-counter
Vue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>' })
-
Components are reusable Vue instances with a name: in this case,
<button-counter>
. We can use this component as a custom element inside a root Vue instance created withnew Vue
:<div id="components-demo"> <button-counter></button-counter> </div>
new Vue({ el: '#components-demo' })
-
-
Components accept the same options as
new Vue
, such asdata
,computed
,watch
,methods
, and lifecycle hooks. The only exceptions are a few root-specific options likeel
. -
data
option MUST be a function, so that each instance can maintain an independent copy of the returned data object:data: function () { return { count: 0 } }
-
props
- We pass data to child components using
props
!
Vue.component('blog-post', { props: ['title'], template: '<h3>{{ title }}</h3>' })
<blog-post title="My journey with Vue"></blog-post> <blog-post title="Blogging with Vue"></blog-post> <blog-post title="Why Vue is so fun"></blog-post>
- We pass data to child components using
-
Checkout commit
Lesson 9
for more details.-
Child: We emit method name and variantId
methods: { addToCart: function() { this.$emit('add-to-cart', this.variants[this.selectedVariant].variantId); } },
-
Parent component:
<product @add-to-cart="updateCart"></product>
methods: { updateCart(id) { this.cart.push(id); } }
-
-
v-model
: Create a two-way binding on a form input element or a component.- limited to:
<input>
<select>
<textarea>
components
- Modifiers
.lazy
- listen to change events instead of input.number
- cast valid input string to numbers.trim
- trim input
- limited to:
-
Example:
-
@submit.prevent="onSubmit"
use.prevent
to prevent form submission<product-review></product-review>
Vue.component('product-review', { template: ` <form @submit.prevent="onSubmit"> <input v-model="name"> <select v-model.number="rating"> <option></option> <option>1</option> <option>2</option> <option>3</option> </select> <input type="submit" value="Submit"> </form> `, data() { return { name: null } }, methods: { onSubmit() { // do something ... // emit event+data to parent component } } })
-
-
For more info, check commit
1-IntroToVuejs: Forms
on this repository. (Validate form, emit event+data to parent component, append review to existing reviews...)
-
In this lesson :
- We created new component
product-tabs
. - Moved
reviews
andreview-form
to this component. - Finally, we sent event+data from grand child
product-tabs
toproduct
through new Vue instanceeventBus
- We created new component
-
Use
eventBus
(new instance of Vue) to get event from grand child. Usually we get event from childs only.vm.$on
(event
,callback
)-
Listen for a custom event on the current vm. Events can be triggered by vm.$emit. The callback will receive all the additional arguments passed into these event-triggering methods
vm.$on('test', function (msg) { console.log(msg) }) vm.$emit('test', 'hi') // => "hi"
-
https://www.vuemastery.com/courses/real-world-vue-js/real-world-intro/
-
Vue CLI: Command Line Interface
- Allows us to select libraries our project will be using
- Automatically plug them into our project
- It configures webpack.
- JS files, CSS, and dependencies get properly bundled together and optimized upon deployment
- Allows us to write our HTML, CSS & JAvascript however we like.
- We can use single-file .vue components, TypeScript, SCSS, Pug, the latest versions of EXMAScript, etc.
- In development mode, it enables Hot Module Replacement (HMR)
- Your app updates live whenever you save a change.
- Allows us to select libraries our project will be using
-
We install VueCLI using
npm
npm i -g @vue/cli
- We can create our project with VueCLI or VueUI
-
Create a project using the command line
vue create 2-real-world-vue
Select:
- Babel
- Router
- Vuex
- Linter / Formatter
- (EsLint + Prettier)
- Lint on save
- Place config in dedicated config files
- Use npm
-
Run the project
npm run serve
-
Check video to see how to create a project using VueUI
-
Run VueUI using this command:
vue ui
-
How is the app being loaded?
import Vue from "vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; Vue.config.productionTip = false; new Vue({ router, store, render: h => h(App) }).$mount("#app");
App.vue
: is the root componentrouter
: vue routerstore
: for vuex-
// Create our Vue instance new Vue({ // Send our router and store to the Vue instance router, store, // render the App component render: h => h(App) // mount our App to #app }).$mount("#app");
- When our App gets loaded it starts by serving up the
public
directory and loadingindex.html
-
Build process
- Build our app for production
npm run build
- a new directory
/dist
is created. It contains ourjs
&css
files
- Check video course
-
Vuejs uses
vue-router
for client side routing. -
As you can see in
package.json
, we havevue-router
as a dependecy"dependencies": { "core-js": "^3.6.4", "vue": "^2.6.11", "vue-router": "^3.1.6", "vuex": "^3.1.3" },
-
2-real-world-vue/src/router/index.js
import Home from "../views/Home.vue"; //... { path: "/", name: "home", component: Home },
-
We use the router inside our 2-real-world-vue/src/main.js
import router from "./router"; new Vue({ router, // ....
- With ES6, this is the same as:
import router from "./router"; new Vue({ router: router, // ....
-
<router-link />
is a component that looks for path inside our router.-
<router-view />
is where our vue is rendered<div id="app"> <div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </div> <router-view /> </div>
-
-
Use named routes:
<router-link :to="{name: 'home'}">Home</router-link>
-
Redirect url:
- 1st way:
{ path: "/about-us", name: "about", component: About }, { path: "/about", redirect: { name: "about"} },
- 2nd way:
{ path: "/about-us", name: "about", component: About, alias: "/about" }
- 1st way:
-
Create dynamic route.
{ path: "/user/:username", name: "User", component: User, props: true },
- Create Link
<template> <div id="app"> <div id="nav"> <router-link :to="{ name: 'Home' }">Home</router-link> | <router-link :to="{ name: 'User', params: { username : 'Oussama' } }">Oussama</router-link> </div> <router-view /> </div> </template>
<template> <div class="about"> <h1>This is {{ $route.params.username }} page</h1> </div> </template> <script> export default { props: ["username"] }; </script>
- Using
props: ["username"]
, our component can now be more easily reused as child component somewhere sending inusername
as aprop
-
The hash
#
is removed from the URL by addingmode: "history",
to our 2-real-world-vue/src/router/index.jsconst router = new VueRouter({ mode: "history", base: process.env.BASE_URL, routes });
- Here comes a problem, though: Since our app is a single page client side app, without a proper server configuration, the users will get a 404 error if they access
http://oursite.com/user/id
directly in their browser. Read more about server config
- Here comes a problem, though: Since our app is a single page client side app, without a proper server configuration, the users will get a 404 error if they access
-
Component file structure:
<template> <div> ..... </div> </template> <script> export default { } </script> <style scoped> </style>
scoped
scope the style to this component
-
Example:
EventCard
is not a global component because we're using it only insideEventList
. We cannot acces it globally from other components!import EventCard from '@/components/EventCard.vue'; export default { components: { EventCard } }
-
What components should be globally registered?
- The components that you use through your application frequently
-
Global components are registered in
main.js
- Manual global component registration
import BaseIcon from "./components/BaseIcon.vue"; Vue.component('BaseIcon', BaseIcon);
- Automatic Global Registration of Base Components
import upperFirst from 'lodash/upperFirst' import camelCase from 'lodash/camelCase' const requireComponent = require.context( // The relative path of the components folder './components', // Whether or not to look in subfolders false, // The regular expression used to match base component filenames /Base[A-Z]\w+\.(vue|js)$/ ) requireComponent.keys().forEach(fileName => { // Get component config const componentConfig = requireComponent(fileName) // Get PascalCase name of component const componentName = upperFirst( camelCase( // Gets the file name regardless of folder depth fileName .split('/') .pop() .replace(/\.\w+$/, '') ) ) // Register component globally Vue.component( componentName, // Look for the component options on `.default`, which will // exist if the component was exported with `export default`, // otherwise fall back to module's root. componentConfig.default || componentConfig ) })
- Manual global component registration
-
Example of slot:
<template> <div class="icon-wrapper"> <svg class="icon" :width="width" :height="height"> <use v-bind="{ 'xlink:href': 'feather-sprite.svg#'+name}"></use> </svg> <slot></slot> </div> </template>
<BaseIcon name="users"> {{ event.attendees.length }} attendees </BaseIcon>
-
Name slot:
- MediaBox.vue:
<slot name="heading"></slot> <slot name="paragraph"></slot>
- Parent.vue
<template> <MediaBox> <h2 slot="heading">Oussama GHAIEB</h2> <p slot="paragraph">My words...</p> </MediaBox> </template>
- MediaBox.vue:
-
We need a library to do API calls AXIOS
-
GET
,POST
,PUT
andDELETE
requests- Add authentication to each request
- Set timeouts if request take too long
- Configure defaults for every request
- Intercept requests to create middleware
- Handle errors and cancel requests properly
- Properly serialize and deserialize requests and responses
-
Axios call are run asynchronously
- The code doesn't wait to be completed , before continuing
apiCall() { console.log('Enter'); axios.get('https://example.com/events') .then(response => console.log(response.data)) .catch(error => console.log(error)); console.log('Exit'); }
- Result:
//result > apiCall() Enter Exit { "events": [...] }
- The code doesn't wait to be completed , before continuing
-
We'll need get a full fake REST API using json-server
npm install -g json-server
- We'll use
db.json
to mock api data
json-server --watch db.json
Now go to:
http://localhost:3000/events
- We'll use
-
2-real-world-vue/src/services/EventService.js
import axios from 'axios'; const apiClient = axios.create({ baseURL: 'http://localhost:3000', withCredentials: false, headers: { Accept: 'application/json', 'content-type': 'application/json' } }); export default { getEvents() { return apiClient.get('/events'); }, getEvent(id) { return apiClient.get('/events/' + id); } }
import EventCard from '@/components/EventCard.vue'; import EventService from '@/services/EventService.js'; export default { components: { EventCard }, data() { return { events: [] } }, created() { EventService.getEvents() .then(response => { this.events = response.data }) .catch(error => console.log(error)) } }
-
json-server --watch db.json
-
Interpolated values in templates can have simple Javascript expressions.
{{ myStringVar.split('').reverse().join('') }}
-
Lesson 27: Vuejs app structure
-
Lesson 31: Recreate a view app (src/mains.js and src/App.vue)
-
Lesson 32:
export default { name: 'App' };
- It's a good practice to name your component using
name
. This would be helpful when using some debug tools!
- It's a good practice to name your component using
-
Lesson 33:
- import
App.vue
inside ourmain.js
import Vue from 'vue'; import App from './App'; new Vue({ render: function (createElement) { return createElement(App); } });
- This how it looks when creating new app using VueCli
new Vue({ render: h => h(App) })
- import
-
Lesson 36:
- Wire components using this syntax
components: { SearchBar, OtherComponent, CoolComponent }
import SearchBar from "@/components/SearchBar.vue"; export default { name: "App", components: { SearchBar } }
- Wire components using this syntax
-
Lesson 37:
`<input @input="onInput" />` onInput(event) { console.log(event.target.value); }
-
Lesson 40: How components communicate:
- Child -----(event)----> Parent
- Parent ----(props)----> Child
-
Lesson 48:
- Inside a view instance,
data
can be an object or function - Inside a view component,
data
must be a function that returns an object
- Inside a view instance,
-
Lesson 50:
- Parent:
<template> <div> <SearchBar @termChange="onTermChange" /> <VideoList :videos="videos"/> </div> </template>
-
This
<VideoList :videos="videos"/>
is the same as<VideoList v-bind:videos="videos"/>
-
Child:
<template> <div> <h2>Video List</h2> <ul v-if="videos"> <li v-for="video in videos" :key="video.key">{{ video.snippet.title }}</li> </ul> </div> </template> <script> export default { name: "VideoList", props: [ 'videos' ] } </script>
-
Lesson 55:
- Adding a
:key
tov-for
will enhance the performance of our app.
`<VideoListItem v-for="video in videos" :key="video.etag" :video="video" />`
- Adding a
-
Lesson 57:
- Add
css
that affects only the containing component. Just addscoped
tostyle
tag.
<style scoped> // ... </style>
- Add
-
Lesson 60: Computed properties: Example
thumbnailUrl
<template> <li class="list-group-item"> <img :src="thumbnailUrl" alt="video.snippet.title" /> {{ video.snippet.title }} </li> </template> <script> export default { name: "VideoListItem", props: ['video'], computed: { thumbnailUrl() { return this.video.snippet.thumbnails.default.url; } } } </script>
-
Lesson 69: ES2015 Syntax:
- This:
const videoId = this.video.id.videoId; return 'https://www.youtube.com/embed/' + videoId;
- Could be transformed to this:
const { videoId } = this.video.id; return `https://www.youtube.com/embed/${videoId}`;