Search code examples
javascriptvue.jsassociationsvuex

Maintain associated objects in Vue


I'm new to vue, but I carefully read the docs for vue and vuex as well. But I'm stuck with the following issue. I also do think it's neither that I would not get it done somehow, but I'd like to go for a clean and probaply best practive approach.

The Setup: - I have some entities - I have some users - lets say an entity can belong to multiple users

Now what I want to accomplish is to display a list of all users having those checked which are assigned to the entity.

I have the following vuex store:

export default new Vuex.Store({
    state: {
        users: [],
        entities: []
    },
    getters: {
        USERS: state => state.users,
        ENTITIES: state => state.entities,
        ENTITY: state => id => {
            return state.entities.find(entity => entity.id == id)
        }
    },
    mutations: {
        SET_USERS: (state, payload) => {
            state.users = payload
        },
        SET_ENTITIES: (state, payload) => {
            state.entities = payload
        },
        UPDATE_DEVICE: (state, payload) => {
            state.entities = state.entities.map(entity => {
                if(entity.id === payload.id){
                    return Object.assign({}, entity, payload.data)
                }
                return entity
            })
        }
    },
    actions: {
        GET_USERS: async (context) => {
            let { data } = await Axios.get('/api/v1/users')
            context.commit('SET_USERS', data)
        },
        GET_ENTITIES: async (context) => {
            let { data } = await Axios.get('/api/v1/entities')
            context.commit('SET_ENTITIES', data)
        },
        UPDATE_ENTITY: ({commit}, payload) => {
            Axios.put('/api/v1/entities/' + payload.id + '/', payload.data).then((response) => {
                commit('UPDATE_ENTITY', payload)
            });
        }
    }
})

My Entity-Component loads the users from the store within the created hook. The the entity's data is get from the store from the computed property entity(). Also the list of all users is served by a computed property users().

created(){
   if(!this.$store.getters.USERS.length) this.$store.dispatch('GET_USERS')
},
computed: {
   entity(){
      const entityId = this.$route.params.id
      return this.$store.getters.ENTITY(entityId)
   },
   users(){
      return this.$store.getters.USERS
   }
}

Then within the template I show all the users and a checkbox:

<ul>
   <li v-for="(user, i) in users" :key="i">
      <input type="checkbox" :value="user" :id="'user_'+i" :name="'user_'+i" v-model="???" />
      <label :for="'user_'+i">{{user.name}}</label>
   </li>
</ul>

I also have a second list of all users which belong to the entity within the the same component's template like the following which I'd like to keep in sync with the 'selectable list'. So all users with a checked checkbox should be listed in that list:

<ul>
   <li v-for="user in entity.users" :key="user.id">
      {{user.name}}
    </li>
</ul>

And here is where I'm stuck at: should I use a computed property for the device.users with get() and set() and use this as v-model on the checkboxes? I tried that but it hasn't worked because the user object of the all-users list and the objects of the device.users list were not the same objects even if they represent the same user. And at that point I think I'm doing the whole thing way to complex and I'm simply overlooking the common way practiced vue-users would do it.

So long story short: what is the best way to solve this task? I think it's a mostly common task.

Thanks for every answer, if more code / details required I of cause will provide them!


Solution

  • How does the structure of entity look? Assuming they have an array 'users', you could calculate the value for the checkbox by providing a basic javascript function that checks if that user's unique ID is in the list for this entity.

    In computed make a new property (so you don't recalculate the same array for every element in v-for):

    userIdsWithEntity() {
      if (!this.entity) return [];  // necessary in case entity hasn't been instantiated yet
      return this.entity.users.map(x => x.id)
    }
    

    Then provide a simple function to the checkbox value that returns true or false: :value="userIdsWithEntity.includes(user.id)"

    Instead of v-model (which is :value and @input/@change to update the property provided in :value rolled into one directive, so you might get conflicts with your :value definition), use @change to handle the (un)checking of the checkbox, dispatching an action to vuex to remove/add that user to the entity.