Aitch
Aitch

Reputation: 63

Is there a better way to share data between dynamically loaded components?

I recently built a small application with Vue.js and Express.js. There are few components needed to be prepared by the servers, e.g., the combobox for Article.Category and Article.User. The options for these 2 components needed to be rendered from server. I use <component /> as the placeholder for these 2 components in the article edit form:

<component v-bind:is="user_selection_component"></component>
<component v-bind:is="category_selection_component"></component>

I use the template string for initialising the components, the template string result.data.template is passed by server:

let org_data = original_store;
let new_data = () => {

    org_data['remote_options'] = result.data.remote_options;

    //if there is any default value, then assign the value to field referred by "model_name"
    if(model_name && result.data.preset_value){
        let previous_value = $shared.index(org_data, model_name);

        if(!previous_value){
            $shared.index(org_data, model_name, result.data.preset_value);
        }
    }

    return org_data;
}

var default_cb = ()=>{console.info('['+model_name+'].default_cb()')};

let TempComponent = {
    template: result.data.template,
    methods:  component_ref.methods,
    data:     new_data,
    mounted:  cb !== null ? cb.bind(this, org_data) : default_cb.bind(this)
};

app[mount_component] = TempComponent;

Here is the problem, the data method returns a new Observable store for the dynamically loaded components, they don't share the same store object with the parent component, which is the article edit from. Hence, if I want to modify the category field value or user field value, I have to let the callback function cb to accept the store objects of these 2 dynamically loaded components. Otherwise, from the parent component, I could not modify the values in these 2 components.

So I came up with a temporary workaround, I passed the setter method as the callback function to these dynamically loaded functions:

let set_user_id = null;
let set_cate_id = null;

(org_store) => { set_user_id = (new_id) => { org_store.form.user_id = new_id; }}
(org_store) => { set_cate_id = (new_id) => {org_store.form.category_id = new_id; }}

After I load other components or anytime I want to set the category/user value, I can just call set_user_id($new_user_id) or set_cate_id($new_category_id);

I don't like this work around at all. I tried to use the event handler to emit the new values into these 2 components. But I couldn't access these 2 dynamically loaded component via $ref. Is there a better way to let data be shared between dynamically loaded components? Thanks.

Upvotes: 1

Views: 412

Answers (2)

Roy J
Roy J

Reputation: 43881

If your components will accept props, you can localize your event bus, which is a little nicer than having a global. The parent component creates the bus as a data item:

data() {
  ...
  bus: new Vue()
}

The components accept it as a prop:

<component v-bind:is="user_selection_component" :bus="bus"></component>
<component v-bind:is="category_selection_component" :bus="bus"></component>

and you use it as in your answer, except referring to this.bus instead of just bus.

Upvotes: 2

Aitch
Aitch

Reputation: 63

I don't think what I have now is the best solution. After consulting with other people, I took event bus as the better solution. So I modified my code as:

In my init component:

let org_data = original_store;
let new_data = () => {

    org_data['remote_options'] = result.data.remote_options;

    //if there is any default value, then assign the value to field referred by "model_name"
    if(model_name && result.data.preset_value){
        let previous_value = $shared.index(org_data, model_name);

        if(!previous_value){
            $shared.index(org_data, model_name, result.data.preset_value);
        }
    }

    return org_data;
}

var default_cb = () => { console.info('['+model_name+'].default_cb()') };

let TempComponent = {
    template: result.data.template,
    methods:  component_ref.methods,
    data:     new_data,
    mounted:  cb !== null ? cb.bind(this, org_data) : default_cb.bind(this)
};

bus.$on('set.' + model_name, function(value){
    $shared.index(org_data, model_name, value);
});

The difference is here:

bus.$on('set.' + model_name, function(value){
   $shared.index(org_data, model_name, value);
});

The bus is the common event bus created by Vue():

let bus = new Vue()

From the parent component, I can just use this event bus to emit the event:

bus.$emit('set.form.user_id', this.form.user_id);

I do feel better after changing to this solution. But I still appreciate if there is an even better way. Thanks.

Upvotes: 1

Related Questions