Welcome to VueJS workshop

Excercise 6

Create VueX store with user and module and posts module

Load posts into a store on initial app load

Change posts access to be getters from the store

Integrate login flow with VueX user module

Add login button to redirect to the Login page or present user name when the user is logged in

create add comments functionality to an app (if not logged in, it should be a guest, if logged in, should write user name near the comment)

When filling the login form, log in to dummy API, store user in store and redirect to UserProfile page.

 
 
 
 
 
 
 
 
 
 
 
 

Exercises

Animations and transitions

Vue provides a transition wrapper component, allowing you to add entering/leave transitions for any element or component in the following contexts:

  • Conditional rendering (using v-if)
  • Conditional display (using v-show)
  • Dynamic components
  • Component root nodes

Animations and transitions

 <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}

What happens?

 

  1. Vue will automatically sniff whether the target element has CSS transitions or animations applied. If it does, CSS transition classes will be added/removed at appropriate timings.
  2. If the transition component provided JavaScript hooks, these hooks will be called at appropriate timings.
  3. If no CSS transitions/animations are detected and no JavaScript hooks are provided, the DOM operations for insertion and/or removal will be executed immediately on next frame (Note: this is a browser animation frame, different from Vue’s concept of nextTick).

Enter - leave

  • v-enter:  before element is inserted, removed one frame after element is inserted.
  • v-enter-active: Applied during the entire entering phase. Added before an element is inserted, removed when transition/animation finishes.  used to define the duration, delay and easing
  • v-enter-to: End state for enter. Added one frame after an element is inserted (at the same time v-enter is removed), removed when transition/animation finishes.
  • v-leave: Added immediately when a leaving transition is triggered, removed after one frame.
  • v-leave-active: Applied during the entire leaving phase. Added immediately when leave transition is triggered, removed when the transition/animation finishes. used to define the duration, delay and easing
  • v-leave-to: Added one frame after a leaving transition is triggered (at the same time v-leave is removed), removed when the transition/animation finishes.
2

Enter - leave

CSS Transitions

<div id="example-1">
  <button @click="show = !show">
    Toggle render
  </button>
  <transition name="slide-fade">
    <p v-if="show">hello</p>
  </transition>
</div>
/* Enter and leave animations can use different */
/* durations and timing functions.              */
.slide-fade-enter-active {
  transition: all .3s ease;
}
.slide-fade-leave-active {
  transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active below version 2.1.8 */ {
  transform: translateX(10px);
  opacity: 0;
}

CSS Animations

<div id="example-2">
  <button @click="show = !show">Toggle show</button>
  <transition name="bounce">
    <p v-if="show">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.</p>
  </transition>
</div>
.bounce-enter-active {
  animation: bounce-in .5s;
}
.bounce-leave-active {
  animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}

The difference being that v-enter is not removed immediately after the element is inserted, but on an animationend event.

Custom classes

<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">

<div id="example-3">
  <button @click="show = !show">
    Toggle render
  </button>
  <transition
    name="custom-classes-transition"
    enter-active-class="animated tada"
    leave-active-class="animated bounceOutRight"
  >
    <p v-if="show">hello</p>
  </transition>
</div>
  • enter-class
  • enter-active-class
  • enter-to-class (2.1.8+)
  • leave-class
  • leave-active-class
  • leave-to-class (2.1.8+)

Custom duration

You can specify duration

<transition :duration="1000">...</transition>
<transition :duration="{ enter: 500, leave: 800 }">...</transition>

Transition hooks

<transition
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"

  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled"
>
  <!-- ... -->
</transition>

Transition hooks

// ...
methods: {
  // --------
  // ENTERING
  // --------

  beforeEnter: function (el) {
    // ...
  },
  // the done callback is optional when
  // used in combination with CSS
  enter: function (el, done) {
    // ...
    done()
  },
  afterEnter: function (el) {
    // ...
  },
  enterCancelled: function (el) {
    // ...
  },

  // --------
  // LEAVING
  // --------

  beforeLeave: function (el) {
    // ...
  },
  // the done callback is optional when
  // used in combination with CSS
  leave: function (el, done) {
    // ...
    done()
  },
  afterLeave: function (el) {
    // ...
  },
  // leaveCancelled only available with v-show
  leaveCancelled: function (el) {
    // ...
  }
}

There is a lot more about transitions. check out the docs 

https://vuejs.org/v2/guide/transitions.html

Vue Plugins

  • Plugins that add global methods or properties
  • Plugins that add one or more global assets: directives/filters/transitions etc. e.g. vue-touch
  • Plugins that add some component options by the global mixin. e.g. vue-router
  • Plugins that add some Vue instance methods by attaching them to Vue.prototype.
  • A library that provides an API of its own, while at the same time injecting some combination of the above. e.g. vue-router

VueX Plugins

// calls `MyPlugin.install(Vue)`
Vue.use(MyPlugin)

new Vue({
  //... options
})

Plugin should expose an install method. The method will be called with the Vue constructor as the first argument, along with possible options:

Vue Plugins

MyPlugin.install = function (Vue, options) {
  // 1. add global method or property
  Vue.myGlobalMethod = function () {
    // some logic ...
  }

  // 2. add a global asset
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // some logic ...
    }
    ...
  })

  // 3. inject some component options
  Vue.mixin({
    created: function () {
      // some logic ...
    }
    ...
  })

  // 4. add an instance method
  Vue.prototype.$myMethod = function (methodOptions) {
    // some logic ...
  }
}

Mixins

// define a mixin object
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// define a component that uses this mixin
var Component = Vue.extend({
  mixins: [myMixin]
})

var component = new Component() // => "hello from mixin!"

Mixins

When a mixin and the component itself contain overlapping options, they will be “merged” using appropriate strategies.

For example, data objects undergo a recursive merge, with the component’s data taking priority in cases of conflicts.

Custom directives

// Register a global custom directive called `v-focus`
Vue.directive('focus', {
  // When the bound element is inserted into the DOM...
  inserted: function (el) {
    // Focus the element
    el.focus()
  }
})

Hook Functions

A directive definition object can provide several hook functions (all optional):

  • bind: called only once, when the directive is first bound to the element

  • inserted: called when the bound element has been inserted into its parent node

  • update: called after the containing component’s VNode has updated, but possibly before its children have updated.

  • componentUpdated: called after the containing component’s VNode and the VNodes of its children have updated.

  • unbind: called only once, when the directive is unbound from the element.

 

Directive Hook Arguments

  • ​​el: The element the directive is bound to
  • binding: An object containing the following properties.
    • name: The name of the directive, without the v- prefix.
    • value: The value passed to the directive. For example in v-my-directive="1 + 1", the value would be 2.
    • oldValue: The previous value, only available in update and componentUpdated. It is available whether or not the value has changed.
    • expression: The expression of the binding as a string. For example in v-my-directive="1 + 1", the expression would be "1 + 1".
    • arg: The argument passed to the directive, if any.
    • modifiers: An object containing modifiers, if any.
  • vnode: The virtual node produced by Vue’s compiler
  • oldVnode: The previous virtual node, only available in the update and componentUpdated hooks.

Dynamic args

Directive arguments can be dynamic.

v-mydirective:[argument]="value"

Shorthand

In many cases, you may want the same behavior on bind and update, but don’t care about other

 
Vue.directive('color', function (el, binding) {
  el.style.color = binding.value
})

Render Functions

  • Used when you need JS power over the template
  • Render functions are closer to compiler

 

Render Functions

<script type="text/x-template" id="anchored-heading-template">
  <h1 v-if="level === 1">
    <slot></slot>
  </h1>
  <h2 v-else-if="level === 2">
    <slot></slot>
  </h2>
  <h3 v-else-if="level === 3">
    <slot></slot>
  </h3>
  <h4 v-else-if="level === 4">
    <slot></slot>
  </h4>
  <h5 v-else-if="level === 5">
    <slot></slot>
  </h5>
  <h6 v-else-if="level === 6">
    <slot></slot>
  </h6>
</script>
Vue.component('anchored-heading', {
  render: function (createElement) {
    return createElement(
      'h' + this.level,   // tag name
      this.$slots.default // array of children
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

Create element

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // An HTML tag name, component options, or async
  // function resolving to one of these. Required.
  'div',

  // {Object}
  // A data object corresponding to the attributes
  // you would use in a template. Optional.
  {
    // (see details in the next section below)
  },

  // {String | Array}
  // Children VNodes, built using `createElement()`,
  // or using strings to get 'text VNodes'. Optional.
  [
    'Some text comes first.',
    createElement('h1', 'A headline'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

You might want to switch to JSX

import AnchoredHeading from './AnchoredHeading.vue'

new Vue({
  el: '#demo',
  render: function (h) {
    return (
      <AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  }
})

Functional components

Vue.component('my-component', {
  functional: true,
  // Props are optional
  props: {
    // ...
  },
  // To compensate for the lack of an instance,
  // we are now provided a 2nd context argument.
  render: function (createElement, context) {
    // ...
  }
})
<template functional>
</template>

Different edge cases

Vue Reactivity system in depth

Security

Folder structure in big projects

Sort by Feature vs Sort by Type

Form Validations

import Vue from 'vue'
import Vuelidate from 'vuelidate'
Vue.use(Vuelidate)
import { validationMixin } from 'vuelidate'

var Component = Vue.extend({
  mixins: [validationMixin],
  validations: { ... }
})

Or use as a mixin inside component

import { required, minLength, between } from 'vuelidate/lib/validators'

export default {
  data() {
    return {
      name: '',
      age: 0
    }
  },
  validations: {
    name: {
      required,
      minLength: minLength(4)
    },
    age: {
      between: between(20, 30)
    }
  }
}
<div>
  <div class="form-group" :class="{ 'form-group--error': $v.form.userName.$error }">
    <label class="form__label">Username</label>
    <input class="form__input" v-model.trim="$v.form.userName.$model"/>
  </div>
  <div class="error" v-if="!$v.form.userName.required">Field is required.</div>
  <div class="error" v-if="!$v.form.userName.minLength">Field must have at least {{ $v.form.userName.$params.minLength.min }} characters.</div>
  <div class="form-group" :class="{ 'form-group--error': $v.form.password.$error }">
    <label class="form__label">Password</label>
    <input class="form__input" v-model.trim="$v.form.password.$model" type="password"/>
  </div>
  <div class="error" v-if="!$v.form.password.required">Field is required.</div>
  <div class="error" v-if="!$v.form.password.minLength">Field must have at least {{ $v.form.password.$params.minLength.min }} characters.</div>
  <div class="form-group" :class="{ 'form-group--error': $v.form.$error }">
    <div class="error" v-if="$v.form.$error">Form is invalid.</div>
  </div>
  <tree-view :data="$v" :options="{rootObjectKey: '$v', maxDepth: 2}"></tree-view>
</div>

Dependency injection with provide and inject

The provide option should be an object or a function that returns an object. This object contains the properties that are available for injection (with inject) into its descendants.

export default {
  name: 'TodoList',
  inject: { fetchTodos: 'fetch' },
  created() {
    this.fetch();
  },

Dependency injection with provide and inject

The provide option should be an object or a function that returns an object. This object contains the properties that are available for injection (with inject) into its descendants.

export default {
  name: 'TodoList',
  inject: { fetchTodos: 'fetch' },
  created() {
    this.fetch();
  },
export default {
  name: 'ProductListingProvider',
  provide: { fetch },
};

Dependency injection with provide and inject

Properties injected that way are not reactive!

Testing

Excercise - write tests

Architecture patterns and best practices

Provider components

<template>
  <DataProvider url="https://jsonplaceholder.typicode.com/users">
    <div v-slot="{ data, loading }">
      <div v-if="loading">Loading...</div>
      <div v-else>
        <h2>Result: {{ data.length }} users</h2>
        <p v-for="user in data" :key="user.id">{{ user.name }}</p>
      </div>
    </div>
  </DataProvider>
</template>
import axios from "axios";

export default {
  props: ["url"],
  data: () => ({
    data: null,
    loaded: false
  }),
  created() {
    axios.get(this.url).then(({ data }) => {
      this.data = data;
      this.loaded = true;
    });
  },
  render() {
    const slot = this.$scopedSlots.default({
      loading: !this.loaded,
      data: this.data
    });

    return Array.isArray(slot) ? slot[0] : slot;
  }
};

Functional wrapper components

// src/components/factories/container.js
export default (Component, props) => ({
  functional: true,
  render(h) {
    return h(Component, { props });
  }
});
import containerFactory from './factories/container';
import apiService from '../services/apiService';

import TodoListContainer from './TodoListContainer';

export default containerFactory(TodoListContainer, {
  api: apiService
});

Repository pattern

Repository.js -> postsRepository.js -> RepositoryFactory

Vue 3 composition API syntax

GraphQL

Thanks

https://youtube.com/c/VladimirNovickDev

https://twitch.tv/vnovick

https://vnovick.com

  @VladimirNovick

Made with Slides.com