Vue practice (V) – deep component dependency injection / event bus (component loose coupling)

Time:2022-1-22

0. Background

As we all know, the communication between components, especially the data transfer between parent and child components, is carried out through props attribute.
In fact, in addition to this method, we have other alternative methods, which we call dependency injection.
Dependency injection eliminates the need for tight coupling between components.
In addition, we also have an event bus like way to enhance the communication between components,
These will be introduced later.

 

1. Some key concepts

  • Dependency injection

Dependency injection allows a component to provide services to any of its child components (descendant components).
These services can be values, objects, or functions.

Dependency injection injects services through the provide attribute.


  • Event bus

The event bus is based on the dependency injection function.
It is used to provide a mechanism to send and receive custom events.

Events send and receive events through the $emit and $on methods.


 

2. Preparation

2-1) new project and basic settings

a. New project

vue create productapp --default

b. Basic settings
package.json

"eslintConfig": {
    ...
    "rules": {
        "no-console": "off",
        "no-unused-vars": "off"
     },
     ...
}

c. Basic dependency

npm install b[email protected]

 

3. Product Homepage

3-1) new product display page

Under Src / components folder, create a new productdisplay vue

<template>
    <div>
        <table>
            <tr>
                <th>ID</th><th>Name</th><th>Price</th><th></th>
            </tr>
            <tbody>
                <tr v-for="p in products" v-bind:key="p.id">
                    <td>{{ p.id }}</td>
                    <td>{{ p.name }}</td>
                    <td>{{ p.price | currency }}</td>
                    <td>
                        <button v-on:click="editProduct(p)">
                            Edit
                        </button>
                    </td> 
                </tr>
            </tbody>
        </table>
        <div>
            <button v-on:click="createNew">
                Create New
            </button>
        </div>
    </div>
</template>

<script>
    export default {
        data: function () {
            return {
                products: [
                    { id: 1, name: "Kayak", price: 275 },
                    { id: 2, name: "Lifejacket", price: 48.95 },
                    { id: 3, name: "Soccer Ball", price: 19.50 },
                    { id: 4, name: "Corner Flags", price: 39.95 },
                    { id: 5, name: "Stadium", price: 79500 }]
            } 
        },
        filters: {
            currency(value) {
                return `$${value.toFixed(2)}`;
            }
        }, 
        methods: {
            createNew() {
            },
            editProduct(product) {
            }
        } 
    }
</script>


3-2) create a new general editing component

Under Src / components folder, create a new editorfield vue

<template>
    <div>
        <label>{{label}}</label>
        <input v-model.number="value" />
    </div>
</template>

<script>
export default {
    props: ["label"],
    data: function () {
        return {
            value: ""
        } 
    }
} 
</script>

Code interpretation:

The component is a general editing component, which is composed of a label and a text box.
We will refer to it later in the product editing component.


3-3) create a new product editing component

Under Src / components folder, create a new producteditor vue

<template>
    <div>
        <editor-field label="ID" />
        <editor-field label="Name" />
        <editor-field label="Price" />
        <div>
            <button v-on:click="save">
                {{ editing ? "Save" : "Create" }}
            </button>
            <button v-on:click="cancel">Cancel</button>
        </div>
    </div>
</template>

<script>
    import EditorField from "./EditorField";

    export default {
        data: function () {
            return {
                editing: false,
                product: {
                    id: 0,
                    name: "",
                    price: 0 
                }
            } 
        },
        components: { EditorField },
        methods: {
            startEdit(product) {
                this.editing = true;
                this.product = {
                    id: product.id,
                    name: product.name,
                    price: product.price
                } 
            },
            startCreate() {
                this.editing = false;
                this.product = {
                    id: 0,
                    name: "",
                    price: 0
                }; 
            },
            save() {
                // TODO - process edited or created product
                console.log(`Edit Complete: ${JSON.stringify(this.product)}`);
                this.startCreate();
            }, 
            cancel() {
                this.product = {};
                this.editing = false;
            }
        } 
    }
</script>

Code interpretation:

We call the previously defined general editing component through editor field.


 

4. Dependency injection

4-1) defining services

On app Services are defined in Vue as follows:

<template>
    <div>
        <div>
            <div>
                <product-display></product-display>
            </div>
            <div>
                <product-editor></product-editor>
            </div>
        </div>
    </div>
</template>

<script>
    import ProductDisplay from "./components/ProductDisplay";
    import ProductEditor from "./components/ProductEditor";

    export default {
        name: 'App',
        components: { ProductDisplay, ProductEditor },
        provide:  function() {
            return {
                colors: {
                    bg: "bg-secondary",
                    text: "text-white"
                } 
            }
        }
    } 
</script>

Code interpretation:

Provide a service through the provide attribute, which exposes an object and provides background and foreground.


4-2) consuming services through dependency injection

Modify editorfield. In Src / components folder Vue is as follows:

<template>
    <div>
    <label>{{label}}</label>
    <input v-model.number="value"
           v-bind:class="[colors.bg, colors.text]" />
    </div>
</template>

<script>
export default {
    props: ["label"],
    data: function () {
        return {
            value: ""
        } 
    },
    inject: ["colors"]
} 
</script>

Code interpretation:

The colors object service defined by provide in the parent component before injection of the inject attribute.

The effects are as follows:

Vue practice (V) - deep component dependency injection / event bus (component loose coupling)

image.png


 

5. Event bus

Event bus actually passes a Vue object through dependency injection.

5-1) add bus

Main. In the SRC folder JS add an event bus as follows:

...

new Vue({
    render: h => h(App), 
    provide: function () {
          return {
              eventBus: new Vue()
          } 
    }
}).$mount('#app')


5-2) send events via bus

Modify productdisplay Productdisplay. Under Vue folder Vue, as follows:

... 
<script>
    export default {
        ...
        methods: {
            createNew() {
                this.eventBus.$emit("create");
            },
            editProduct(product) {
                this.eventBus.$emit("edit", product);
            } 
        },
        inject: ["eventBus"]
    } 
</script>

Code interpretation:

1. Before injection, in main JS to send events.
2. The $emit method sends an event with the event name in quotation marks and additional parameters.
3. The event name must remain unique throughout the application.


5-3) receive events via bus

Modify the product Editor under Src / components folder Vue, as follows:

...
<script>
    ...
    export default {
        ...
        methods: {
            startEdit(product) {
                this.editing = true;
                this.product = {
                    id: product.id,
                    name: product.name,
                    price: product.price
                } 
            },
            startCreate() {
                this.editing = false;
                this.product = {
                    id: 0,
                    name: "",
                    price: 0
                }; 
            },
            save() {
                this.eventBus.$emit("complete", this.product);
                this.startCreate();
                console.log(`Edit Complete: ${JSON.stringify(this.product)}`);
            },
            ...
        },
        inject: ["eventBus"],
        created() {
            this.eventBus.$on("create", this.startCreate);
            this.eventBus.$on("edit", this.startEdit);
        }
    } 
</script>

Code interpretation:

1. The $on method receives events. The first parameter is the event name and the second is the local method called.
2. In the save method, we continue to send events to subordinate components through the $emit method.


Modify productdisplay. Under Src / components folder Vue, as follows:

...
<script>
    import Vue from "vue";

    export default {
        ...
        methods: {
            ...
            processComplete(product) {
                let index = this.products.findIndex(p => p.id == product.id);
                if (index == -1) {
                    this.products.push(product);
                } else {
                    Vue.set(this.products, index, product);
                }
            }
        },
        inject: ["eventBus"], 
        created() {
            this.eventBus.$on("complete", this.processComplete);
        }
    } 
</script>

Code interpretation:

The a. $on method receives the completed event and then calls the processComplete method to handle it.

The effects are as follows:

Vue practice (V) - deep component dependency injection / event bus (component loose coupling)

image.png


 

6. Local bus

The way you can create a local bus only corresponds to the service of a component.

Modify producteditor Vue, as follows:

<script>
    ...
    export default {
        data: function () {
            return {
                ...
                localBus: new Vue()
            } 
        },
        ...
        methods: {
            ...
            inject: ["eventBus"], 
            provide: function () {
                return {
                    editingEventBus: this.localBus
                } 
            },
            created() {
                this.eventBus.$on("create", this.startCreate); 
                this.eventBus.$on("edit", this.startEdit); 
                this.localBus.$on("change",
                            (change) => this.product[change.name] = change.value);
            },
            watch: {
                product(newValue, oldValue) {
                    this.localBus.$emit("target", newValue);
                }
            }
        }
    } 
</script>


Modify editorfield. In Src / components folder Vue, as follows:

<script>
    export default {
        props: ["label", "editorFor"],
        inject: {
            ...
            editingEventBus: "editingEventBus"
        },
        watch: {
            value(newValue) {
                this.editingEventBus.$emit("change",
                    { name: this.editorFor, value: this.value});
            } 
        },
        created() {
            this.editingEventBus.$on("target",
                (p) => this.value = p[this.editorFor]);
        }
    } 
</script>

The effects are as follows:

Vue practice (V) - deep component dependency injection / event bus (component loose coupling)

image.png

Recommended Today

Big data Hadoop — spark SQL + spark streaming

catalogue 1、 Spark SQL overview 2、 Sparksql version 1) Evolution of sparksql 2) Comparison between shark and sparksql 3)SparkSession 3、 RDD, dataframes and dataset 1) Relationship between the three 1)RDD 1. Core concept 2. RDD simple operation 3、RDD API 1)Transformation 2)Action 4. Actual operation 2)DataFrames 1. DSL style syntax operation 1) Dataframe creation 2. SQL […]