Class-Based Component with Vue and Typescript

Previously, We have created a ToggleButton component with Vue. We have used the standard way to build the component. In this article, we are going to create the class-based component with TypeScript. We have used the followings for toggle button component i.e.

The same we are going to achieve through the class-based component. It is not a recommended way but if you enjoyed writing codes through Classes and TypeScript then you can.

If you are starting through a fresh project with Vue CLI then you have to add the TypeScript to your project through custom setting during create project.

Or you can add the following dependency packages through npm.

npm i vue-class-component vue-property-decorator -D

Your Class-based Component

Now create a ToggleButton.vue file under src/component directory and paste the following codes.

<template>
    <label for="_button" class="toggle__button">
        <span class="toggle__label">Off</span>

        <input type="checkbox" id="_button">
        <span class="toggle__switch"></span>
    </label>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class ToggleButton extends Vue {}
</script>

As you can see, we have used the vue-property-decoration package. The @Component is a decoration that indicates the class is a vue component. Note that our class-based component must extend the parent class i.e. Vue.

Not doubt that It’s pretty simple with a standard way to create the component.

<script>
export default {}
</script>

Component Properties

Let’s define the properties of our class-based component. We are going to use the @Prop(), decorator. Take a look at the codes.

<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';

@Component
export default class ToggleButton extends Vue {

    @Prop({default: false})
    disabled: boolean

    @Prop({default: false})
    defaultState: boolean
}
</script>

In my case, we have to define the default value for the related properties. Here we defined the default value i.e. true for disabled properties that will accept the boolean type value.

@Prop({default: false})
disabled: boolean

Which is similar to this code.

props: {
    disabled: {
        type: Boolean,
        default: false     
    }
}

And if you don’t want to define the default value then you can use the @Prop() decorator similar to this code.

@Prop()
disabled: boolean

Property ‘disabled’ has no initializer and is not definitely assigned in the constructor

You may get an error while defining the component properties. That is because of the Strict Class Initialization in TypeScript latest version. You need to add ! with property to fix the property initializer error.

@Prop({default: false})
disabled!: boolean

@Prop({default: false})
defaultState!: boolean

Initial Data Declartion

Let’s move to the initial data declaration section. The Initial data can be declared as instance property such as:

currentState: boolean = true

Here the currentState is the property of the class with the default true value that only accepts the boolean value. Which is similar to the following codes.

data() {
    return {
        currentState: this.defaultState
    }
}

The complete code for our component should look like this:

import { Component, Vue, Prop } from 'vue-property-decorator';

@Component
export default class ToggleButton extends Vue {

    @Prop({default: false})
    disabled: boolean

    @Prop({default: false})
    defaultState: boolean

    /**
     * Initial data
     */
    currentState: boolean = this.defaultState
}

Computed Properties

As we know that computed properties are by default getter-only. We just have to add the get before the method to create the computed property. This is how we can define the computed properties in the class-based component.

get isActive() {
    return this.currentState
}

Which is similar to the codes like:

computed: {
    isActive() {
       return this.currentState;
    }
}

But what if we need to set the computed property? Let’s understand it with another piece of code.

get checkedValue(): boolean {
    return this.currentState
}

set checkedValue(newValue) {
    this.currentState = newValue;
}

Here we have defined a computed property i.e. checkedValue() with getter and setter. Which is similar to these code.

checkedValue: {
    get() {
        return this.currentState;
    },

    set(newValue) {
        this.currentState = newValue;
    }
}

Isn’t easy?

Watchers

The next step is defining the watchers. The @Watch() decorator is used to defining the watchers.

@Watch('defaultState')
onPropertyChanged() {
    this.currentState = this.defaultState
}

Here we have attached the @Watch() decorator to the defaultState property. Which is similar to the following codes.

watch: {
    defaultState: function defaultState() {
        this.currentState = Boolean(this.defaultState)
    }
}

Complete Class-based Component Codes

<template>
    <label for="_button" :class="{'active': isActive}" class="toggle__button">
        <span v-if="isActive" class="toggle__label">{{ enableText }}</span>
        <span v-if="! isActive" class="toggle__label">{{ disableText }}</span>

        <input type="checkbox" id="_button" v-model="checkedValue">
        <span class="toggle__switch"></span>
    </label>
</template>

<script lang="ts">
import { Component, Vue, Prop, Emit, Watch } from 'vue-property-decorator';

@Component
export default class ToggleButton extends Vue {

    @Prop({default: false})
    disabled!: boolean

    @Prop({default: false})
    defaultState!: boolean

    @Prop({default: 'On'})
    labelEnableText!: string

    @Prop({default: 'Off'})
    labelDisableText!: string

    @Emit()
    change(): boolean {
        return this.currentState;
    }


    /**
     * Initial data
     */
    currentState: boolean = this.defaultState

    get isActive(): boolean {
        return this.currentState
    }

    get enableText(): string {
        return this.labelEnableText
    }

    get disableText(): string {
        return this.labelDisableText
    }

    get checkedValue(): boolean {
        return this.currentState
    }

    set checkedValue(newValue) {
        this.currentState = newValue;
    }

    @Watch('defaultState')
    onPropertyChanged() {
        this.currentState = this.defaultState
    }
}
</script>

<style lang="scss" scoped>
$base-darken: #666666;
$base-active-darken: #53B883;

.toggle__button {
    vertical-align: middle;
    user-select: none;
    cursor: pointer;

    input[type="checkbox"] {
        opacity: 0;
        position: absolute;
        width: 1px;
        height: 1px;
    }

    .toggle__switch {
        display:inline-block;
        height:12px;
        border-radius:6px;
        width:40px;
        background: rgba($base-darken, 0.2);
        box-shadow: inset 0 0 1px rgba($base-darken, 0.2);
        position:relative;
        margin-left: 10px;
        transition: all .25s;

        &::after,
        &::before {
            content: "";
            position: absolute;
            display: block;
            height: 18px;
            width: 18px;
            border-radius: 50%;
            left: 0;
            top: -3px;
            transform: translateX(0);
            transition: all .25s cubic-bezier(.5, -.6, .5, 1.6);
        }

        &::after {
            background: $base-darken;
            box-shadow: 0 0 1px $base-darken;
        }

        &::before {
            background: $base-darken;
            box-shadow: 0 0 0 3px rgba($base-darken, 0.1);
            opacity:0;
        }
    }
}

.active {
    .toggle__switch {
        background: rgba($base-active-darken, 0.2);
        box-shadow: inset 0 0 1px rgba($base-active-darken, 0.2);

        &::before,
        &::after {
            transform:translateX(0px);
        }

        &::after {
            left: 23px;
            background: $base-active-darken;
            box-shadow: 0 0 1px $base-active-darken;
        }
    }
}
</style>

You can use this component in App.vue file with <ToggleButton /> tag.

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ToggleButton from './components/ToggleButton.vue';

@Component({
    components: {
        ToggleButton
    },
})
export default class App extends Vue {}
</script>