Friday, February 12, 2021

Vue.js - Core Concept


Creating and connecting Vue App instances

First, let's connect Vue app to our html code.

Using 'Vue.createApp' to create a Vue instance.

Then using 'mount' function of instance and CSS selector to bind the Vue app to the html element.

// index.html


    <div id="app"></div>

// app.js


    const app = Vue.createApp({});
    app.mount('#app');


Interpolation and Data Binding

Adding 'properties' to Vue instance for the Vue Reactivity System.

If those 'properties' change, the Vue Reactivity System will 'react' to update views with the new value.

Also, in html file, using '{{ property_name }} for the interpolation(Text).

NOTE: 'data' is a function which need to return an object.

// index.html


<div id="app">
        <!-- Text Interpolation -- >
        <p>{{ message }}</p>
     </div>

// app.js


    const app = Vue.createApp({
        data() {
            return {
                // adding properties
                message: 'Hello World!',
            };
        },
    });
    app.mount('#app');


Binding HTML Attribute

'{{}}', which is called mustaches, cannot be used inside HTML attributes.

If we want to bind the properties to HTML attribute, we need to use v-bind directive provided by Vue.

// index.html


    <div id="app">
        <!-- Not working to use mustaches for HTML attributes -->
        <a href="{{ link }}"></a>

        <!-- Working -->
        <a v-bind:href="link"></a>
    </div>

// app.js


    const app = Vue.createApp({
        data() {
            return {
                // adding properties
                link: 'https://vuejs.org/'
            };
        },
    });
    app.mount('#app');


Methods

Like 'properties', we can add methods to Vue instance as well.

NOTE: 'methods' is an object containing multiple functions.

// index.html


    <div id="app">
        <!-- Call method to show message dynamically -->
        <p>{{ showGreetingMessage() }}</p>
    </div>

// app.js


    const app = Vue.createApp({
        methods: {
            showGreetingMessage() {
                if (Math.random() > 0.5) {
                    return 'Hi';
                } else {
                    return 'How are you?';
                }
            },
        },
    });
    app.mount('#app');


How to access data inside Vue app

Using 'this' to point to the vm instance to get instance properties.

// index.html


    <div id="app">
        <p>{{ showGreetingMessage() }}</p>
    </div>

// app.js

    
const app = Vue.createApp({
        data() {
            return {
                greeting1: 'Hi',
                greeting2: 'How are You?',
            };
        },
        methods: {
            showGreetingMessage() {
                if (Math.random() > 0.5) {
                    // Using this to get instance properties
                    return this.greeting1;
                } else {
                    return this.greeting2;
                }
            },
        },
    });
    app.mount('#app');


Output raw HTML content

Sometimes, we might need to output raw HTML. We can use 'v-html' directive.

// index.html


    <div id="app">
        <!-- Not working -->
        <p>{{ htmlContent }}</p>

        <!-- Working -->
        <p v-html="htmlContent"></p>
    </div>

// app.js


    const app = Vue.createApp({
        data() {
            return {
                htmlContent: '<h1>H1 header</h1>'
            };
        },
    });
    app.mount('#app');


Event Binding

Using 'v-on' directive to listen to DOM events,  and run some relating JavaScript code once they are triggered.

// index.html


    <section id="app">
        <button v-on:click="counter++">Add</button>
        <button v-on:click="counter = counter - 1">Reduce</button>
        <p>Result: {{ counter }}</p>
    </section>

// app.js


    const app = Vue.createApp({
        data() {
            return {
                counter: 0,
            };
        },
    });

    app.mount("#app");

NOTE:  Above code is not optimal practice (or bad practice) because we should avoid putting too much logic in HTML code. HTML code is just only about outputting. We can rewrite it as below version.

// index.html


    <section id="app">
        <button v-on:click="add">Add</button>
        <button v-on:click="reduce">Reduce</button>
        <p>Result: {{ counter }}</p>
    </section>

// app.js

 
   const app = Vue.createApp({
        data() {
            return {
                counter: 0,
            };
        },
        methods: {
            add() {
                this.counter++;
            }

            reduce() {
                this.counter--;
            }
        }
    });

    app.mount("#app");

Also passing arguments to methods is possible.

// index.html


    <section id="app">
        <button v-on:click="add(5)">Add 5</button>
        <button v-on:click="reduce(5)">Reduce 5</button>
        <p>Result: {{ counter }}</p>
    </section>

// app.js


    const app = Vue.createApp({
        data() {
            return {
                counter: 0,
            };
        },
        methods: {
            add(num) {
                this.counter += num;
            }

            reduce(num) {
                this.counter -= num;
            }
        }
    });

    app.mount("#app");


Using the Native Event Object

We might need to access original DOM event in some cases.

For example, we need to get the input value from input event. (Refer to this)

You can pass it into methods using the special $event event.

// index.html


    <section id="app">
        <input
            type="text"
            v-on:input="setMessage($event)"
        >
        <p>{{ message }}</p>
    </section>

// app.js


    const app = Vue.createApp({
        data() {
            return {
                message: "",
            };
        },
        methods: {
            setMessage($event) {
                this.message = $event.target.value;
            },
        }
    });

    app.mount("#app");


Event Modifiers

Sometimes, we need to call 'event.preventDefault()' or 'event.stopPropagation()' to make the behavior under our control.

// index.html


    <section id="app">
        <form>
            <button v-on:click="onBtnClicked($event)">click</button>
        </form>
    </section>

// app.js


    const app = Vue.createApp({
        methods: {
            onBtnClicked($event) {
                $event.preventDefault();
                console.log('Button was clicked');
            },
        }
    });

    app.mount("#app");

Vue provide a handy way to achieve it.

Instead doing in JavaScript code, we can keep those DOM event modification in html code.

 // index.html


    <section id="app">
        <form>
            <button v-on:click.prevent="onBtnClicked">click</button>
        </form>
    </section>

// app.js


    const app = Vue.createApp({
        methods: {
            onBtnClicked() {
                console.log('Button was clicked');
            },
        }
    });

    app.mount("#app");


Two-Way Binding

Data Binding + Event Binding = Two-Way Binding

Below is a simple example.

// index.html


    <section id="app">
        <input
            type="text"
            v-bind:value="name"
            v-on:input="onNameInputChanged($event)"
        >
        <p>{{ name }}</p>
    </section>

// app.js


    const app = Vue.createApp({
        data() {
            return {
                name: "None",
            };
        },
        methods: {
            onNameInputChanged($event) {
                this.name = $event.target.value;
            },
        },
    });

    app.mount("#app");

NOTE:

From this example, since we bind the 'name' property to the 'value' attribute of input element, so we can see the input element having default value. Also, we bind the 'input' DOM event to our JavaScript function which update the value of the 'name' property. Therefore, the combination of Data Binding and Event Binding make those code to support Two-Way Binding. 

Thanks for the Vue, it provide another directive called 'v-model' to make our life easier (less code).

// index.html


    <section id="app">
        <input
            type="text"
            v-model="name"
        >
        <p>{{ name }}</p>
    </section>

// app.js


    const app = Vue.createApp({
        data() {
            return {
                name: "None",
            };
        },
    });

    app.mount("#app");


Computed Properties 

Using function to output in html code in not the best practice.

// index.html


    <section id="app">
        <button v-on:click="onAddBtnClicked">Add</button>
        <p>{{ count }}</p>
        <input
            type="text"
            v-model="name"
        >
        <p>{{ outputWelcomeMessage() }}</p>
    </section>

// app.js


    const app = Vue.createApp({
        data() {
            return {
                count: 0,
                name: "",
            };
        },
        methods: {
            onAddBtnClicked() {
                this.count += 1;
            },
            outputWelcomeMessage() {
                console.log("outputWelcomeMessage was triggered!");

                return 'Hello ' + this.name;
            },
        },
    });

    app.mount("#app");

NOTE:

According to this example, if you click 'Add' button, you will see 'outputWelcomeMessage was triggered!' in console log even we don't touch the input 'name' UI. The reason is that once there is a change, Vue will look through the whole html file to find out which part need to update.
So the function in html will be re-executed by Vue.


Vue provides 'computed' properties to cover this scenario. (Refer to this)

// index.html


    <section id="app">
        <button v-on:click="onAddBtnClicked">Add</button>
        <p>{{ count }}</p>
        <input
            type="text"
            v-model="name"
        >
        <!-- bind to computed property -->
        <p>{{ welcomeMessage }}</p>
    </section>

// app.js


    const app = Vue.createApp({
        data() {
            return {
                count: 0,
                name: "",
            };
        },
        computed: {
            // a computed getter
            welcomeMessage() {
                console.log("welcomeMessage was triggered!");

                return 'Hello ' + this.name;
            },
        },
        methods: {
            onAddBtnClicked() {
                this.count += 1;
            },
        },
    });

    app.mount("#app");

Vue will check the dependencies of computed properties, and it will update the view only its dependencies got changed.


Watchers

It is similar with computed properties, but the use case is different. (Refer to this)

Below example works, but we try not to do it in best practice.

The issue is that if 'welcomeMessage' has multiple dependencies such as 'firstname' and 'lastname', then we need to watch multiple properties.

// index.html


    <section id="app">
        <button v-on:click="onAddBtnClicked">Add</button>
        <p>{{ count }}</p>
        <input
            type="text"
            v-model="name"
        >
        <p>{{ welcomeMessage }}</p>
    </section>

// app.js


    const app = Vue.createApp({
        data() {
            return {
                count: 0,
                name: "",
                welcomeMessage: "",
            };
        },
        watch: {
            name(newValue, oldValue) {
                this.welcomeMessage = 'Hello ' + newValue;
            },
        },
        methods: {
            onAddBtnClicked() {
                this.count += 1;
            },
        },
    });

    app.mount("#app");

The normal use case for watchers is to perform asynchronous operations (Refer to the Vue Document)


Compare between Methods, Computed Properties, and Watch Properties

Methods

* Using for Data Binding and Event Binding in template

* In Data Binding, it will be re-executed for any update even it is not relating (If you really want to use it in this way, that is fine)

Computed Properties

* Using for Data Binding in template

* Will be re-executed if one of its dependencies change

 

Watch Properties

* Not use in template

* Allow you to run some specific code (fetch web api) if the watched properties change


Styling - inline

Using 'v-bind:style' for the lnline styling.

And using camelCase or kebab-case (use quotes with kebab-case) is fine. (Refer to this)

// index.html


    <section id="app">
        <!-- camelCase -->
        <p v-bind:style="{backgroundColor: divColor}"></div>

        <!-- kebab-case with quotes -->
        <p v-bind:style="{'background-color': divColor}"></div>
    </section>

// app.js


    const app = Vue.createApp({
        data() {
            return {
                divColor: "red",
            };
        },
    });

    app.mount("#app");


Styling - Classes

Though inline styling works, but as web developer, we are trained to not use it.

// index.html


    <section id="app">
        <p v-bind:class="isActive? 'active': ''">demo</p>
    </section>

// styles.css


    .active {
        background-color: red;
    }

// app.js


    const app = Vue.createApp({
        data() {
            return {
                isActive: true,
            };
        },
    });

    app.mount("#app");

You might think this format is hard to read if the condition become complicated such as multiple classes messed up together.

Then you can use below 'object' format to set classes.

// index.html


    <section id="app">
        <p v-bind:class="{active: isActive, another_class: true}">hemo</p>
    </section>

// styles.css


    .active {
        background-color: red;
    }

// app.js


    const app = Vue.createApp({
        data() {
            return {
                isActive: true,
            };
        },
    });

    app.mount("#app");


Also, we can use array format to setup multiple classes.

// index.html


    <section id="app">
        <p v-bind:class="[{active: isActive}, another_class]">hemo</p>
    </section>

// styles.css


    .active {
        background-color: red;
    }

// app.js


    const app = Vue.createApp({
        data() {
            return {
                isActive: true,
            };
        },
    });

    app.mount("#app");

No comments:

Post a Comment