Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Vue JS @ MindDoc. The progressive road to onlin...

Vue JS @ MindDoc. The progressive road to online therapy

Creating a single page application is an iterative process, where we should aim for the "good enough" and continuously improve it based on the growing requirements. The current Frontend ecosystem gives us multiple tools that we can employ based on the use cases we might discover on the way. In this presentation, we explain our Vue adventure and development approach with this framework.

Avatar for Darío Blanco

Darío Blanco

April 26, 2018
Tweet

Other Decks in Technology

Transcript

  1. Vue JS @ MindDoc The progressive road to online therapy

    Darío Blanco Iturriaga (CTO Schoen Digital Labs) April 26, 2018
  2. Life is full of uncertainty ▪ Do we implement the

    corporate website on our own? minddoc.de ▪ Angular? React? Vue? ▪ JavaScript? TypeScript? 2
  3. “ “In his writings, a wise Italian says that the

    better is the enemy of good” - Voltaire 3
  4. 5

  5. 6 A simple “Hello World” is always a good sign

    <!-- HTML --> <script src="https://unpkg.com/vue"></script> <div id="app"> <p>{{ message }}</p> </div> // JavaScript new Vue({ el: '#app', data: { message: 'Hello Vue!', }, });
  6. Component based development $props - Pass data to child with

    properties $emit() - Send data to parent with events 7 App SiteHeader Locations router-view Navigation MapClinics Fragment A B C SiteHeader Locations Navigation App MapClinics Fragment A B C
  7. 8 Single File Components HTML template JavaScript code SCSS styles

    <!-- App.vue --> <template> <div id="app"> <p>{{ message }}</p> </div> </template> <script> export default { name: 'App', data() { return { message: 'Hello Vue!' }; } }; </script> <style lang="scss"> @import "~bootstrap/scss/bootstrap"; @import "~@minddoc/mdbootstrap/scss/mdb.scss"; @import "assets/scss/_typography.scss"; </style>
  8. 9 Separation of Concerns <!-- MyComponent.vue --> <template> <div class="container">

    <div class="row justify-content-center"> <div v-if="isAuthenticated" class="col-12"> <h1>Hello {{ username }}</h1> </div> </div> </div> </template> <script src="./MyComponent.js"></script> <style src="./MyComponent.scss"></style> External files
  9. Components Look like classes ▪ data ▪ props ▪ methods

    ▪ computed Types and definitions JavaScript boilerplate code Worse readability Share states Passing data between many of them became a nightmare 10
  10. 11 It could be a class ... Our methods Vue

    lifecycle hook “Private” attributes External provided attributes export default { name: 'Video', props: { username: String, roomType: { type: String, default: 'peer-to-peer', }, }, data() { return { videoRoom: null, roomName: `video-${this.username}`, } }, mounted() { }, methods: { connectToTwilio() { }, joinRoom() { }, leaveRoom() { }, } }
  11. 12 … write “class based” components import Vue from 'vue';

    import Component from 'vue-class-component'; @Component({ props: { username: String, roomType: { type: String, default: 'peer-to-peer', }, } }) export default class Video extends Vue { // initial data videoRoom = null; roomName = `video-${this.username}`; // lifecycle hook mounted() { }; // methods connectToTwilio() { } joinRoom() { } leaveRoom() { } } Reserved method name Our methods Data is implicit “Old style” can be set in decorator (ES6)
  12. 13

  13. Types! Full Vue support (since 2.5.0+) Easy to set with

    vue-cli (3.0+) Experimental decorators 14
  14. import Vue from 'vue'; import { Component, Prop, Vue }

    from 'vue-property-decorator'; import { Room } from 'twilio-video'; @Component export default class Video extends Vue { @Prop() username: string; @Prop({ default: 'peer-to-peer' }) roomType: string; videoRoom: Room = null; roomName = `video-${this.username}`; mounted(): void { }; connectToTwilio(): void { } joinRoom(): boolean { } leaveRoom(): boolean { } } 15 TypeScript implementation Prop decorator defines external attributes Types are optional
  15. 19

  16. Data flow hell Depend on same state ▪ e.g Video

    room details Mutate the same state ▪ e.g Join room API call 20 Pass props Emit events
  17. Vuex Video “store” ▪ Reactive to state changes ▪ Commit

    mutations are required to update the state ▪ Strongly typed 22
  18. 23 Types // store/video/types.ts /* The Twilio video room details

    */ export interface RoomDetails { uniqueName: string; sid: string; } /* The Twilio video room credentials */ export interface RoomCredentials { jwt: string; username: string; role: Role; } /* The Vuex video state */ export interface VideoState { room: RoomDetails | null; token: RoomCredentials | null; error: Error | null; }
  19. 24 // store/video/state.ts import { VideoState } from '@/store/video/types'; /*

    The default video state */ const state: VideoState = { room: null, token: null, error: null, }; export default state; State
  20. 25 Actions // store/video/actions.ts import { ActionTree } from 'vuex';

    import { postRoom } from '@/api/video'; import { RootState } from '@/store/types'; import { VideoState } from '@/store/video/types'; /* The video action tree */ const actions: ActionTree<VideoState, RootState> = { async joinRoom({ commit }, patientUuid: UUID): Promise<void> { try { const roomResponse = await postRoom(patientUuid); commit('joinRoomSuccess', roomResponse); } catch (err) { commit('joinRoomError', err); } }, }; export default actions;
  21. 26 Mutations // store/video/mutations.ts import { MutationTree } from 'vuex';

    import { VideoRoomResponse } from '@/api/video'; import { VideoState } from '@/store/video/types'; /* The video mutation tree */ const mutations: MutationTree<VideoState> = { joinRoomSuccess(state, payload: VideoRoomResponse) { state.room = { uniqueName: payload.roomName, sid: payload.roomSid }; state.token = { jwt: payload.roomJwt, username: payload.username, role: payload.role, }; state.error = null; }, joinRoomError(state, error: Error) { state.error = error; }, }; export default mutations;
  22. 27 Getters // store/video/getters.ts import jwtDecode from 'jwt-decode'; import {

    GetterTree } from 'vuex'; import { RootState } from '@/store/types'; import { VideoState } from '@/store/video/types'; /* The video getter tree */ const getters: GetterTree<VideoState, RootState> = { decodedJwt(state): string | null { const { token } = state; return token ? jwtDecode(token.jwt) : null; }, roomName(state): string | null { const { room } = state; return room ? room.uniqueName : null; }, }; export default getters;
  23. 100% coverage? Requires a lot of effort and discipline to

    achieve. It is worth it. Could be bullshit. Use coverage skip responsibly. /* istanbul ignore <word>[non-word] [optional-docs] */ 30
  24. 31

  25. Mount VS Shallow Mount and render components into a wrapper

    ▪ mount() renders the whole tree ▪ shallow() stubs children 33
  26. Thanks! Any questions? You can find me at ▪ [email protected]

    ▪ Twitter: @darioblanco 34 Join our new TypeScript group! ▪ meetup.com/TypeScript-Munich/