lbris
lbris

Reputation: 1239

Vuejs - update array of an object which is in an array

I'm developing a helpdesk tool in which I have a kanban view.

enter image description here

I previously used nested serializers in my backend and I managed to have everything working with a single query but it's not scalable (and it was ugly) so I switched to another schema :

  1. I query my helpdesk team ('test' in the screenshot)
  2. I query the stages of that team ('new', 'in progress')
  3. I query tickets for each stage in stages

So when I mount my component, I do the following :

async mounted () {
  if (this.helpdeskTeamId) {
    await this.getTeam(this.helpdeskTeamId)
    if (this.team) {
      await this.getTeamStages(this.helpdeskTeamId)
      if (this.stages) {
        for (let stage of this.stages) {
          await this.getStageTickets(stage)
        }
      }
    }
  }
},

where getTeam, getTeamStages and getStageTickets are :

async getTeam (teamId) {
  this.team = await HelpdeskTeamService.getTeam(teamId)
},

async getTeamStages (teamId) {
  this.stages = await HelpdeskTeamService.getTeamStages(teamId)
  for (let stage of this.stages) {
    this.$set(stage, 'tickets', [])
  }
},

async getStageTickets (stage) {
  const tickets = await HelpdeskTeamService.getTeamStageTickets(this.helpdeskTeamId, stage.id)
  // tried many things here below but nothing worked.
  // stage.tickets = stage.tickets.splice(0, 0, tickets)
  // Even if I try to only put one :
  // this.$set(this.stages[this.stages.indexOf(stage)].tickets, 0, tickets[0])
  // I see it in the data but It doesn't appear in the view...
  // Even replacing the whole stage with its tickets :
  // stage.tickets = tickets
  // this.stages.splice(this.stages.indexOf(stage), 1, stage)
},

In getTeamStages I add an attribute 'tickets' to every stage to an empty list. The problem is when I query all the tickets for every stage. I know how to insert a single object in an array with splice or how to delete one object from an array but I don't know how to assign a whole array to an attribute of an object that is in an array while triggering the Vue reactivity. Here I'd like to put all the tickets (which is a list), to stage.tickets.

Is it possible to achieve this ?

If not, what is the correct design to achieve something similar ?

Thanks in advance !

EDIT:

It turns out that there was an error generated by the template part. I didn't think it was the root cause since a part of the view was rendered. I thought that it would have prevent the whole view from being rendered if it was the case. But finally, in my template I had a part doing stage.tickets.length which was working when using a single query to populate my view. When making my API more granular and querying tickets independently from stages, there is a moment when stage has no tickets attribute until I set it manually with this.$set(stage, 'tickets', []). Because of that, the template stops rendering and raises an issue. But the ways of updating my stage.tickets would have worked without that template issue.

Upvotes: 3

Views: 5177

Answers (2)

Branco Medeiros
Branco Medeiros

Reputation: 759

A possible solution would be to watch the selected team and load the values from there. You seem to be loading everything from the mounted() hook, and I suspect this won't actually load all the content on demand as you'd expect.

I managed to make it work here without needing to resort to $set magic, just the pure old traditional vue magic. Vue will notice the properties of new objects and automatically make then reactive, so if you assign to them later, everything will respond accordingly.

My setup was something like this (showing just the relevant parts) -- typing from memory here, beware of typos:


  data(){
    teams: [],
    teamId: null,
    team: null
  },
  
  watch:{
    teamId(v){
      this.refreshTeam(v)
    } 
  },

  methods: {
    async refreshTeam(id){
      let team = await fetchTeam(id)
      if(!team) return

      //here, vue will auomaticlly make this.team.stages reactive
      this.team = {stages:[], ...team}

      let stages = await fetchStages(team.id)
      if(!stages) return

      //since this.team.stages is reactive, vue will update reactivelly
      //turning the {tickets} property of each stage reactive also
      this.team.stages = stages.map(v => ({tickets:[], ...v}))

      for(let stage of this.team.stages){
        let tickets = await fetchTickets(stage.id)
        if(!tickets) continue

        //since tickets is reactive, vue will update it accordingly
        stage.tickets = tickets

      }
    }
  },
  
  async mounted(){
    this.teams = fetchTeams()
  }
   

Notice that my 'fetchXXX' methods would just return the data retrieved from the server, without trying to actually set the component data

Edit: typos

Upvotes: 0

A Farmanbar
A Farmanbar

Reputation: 4788

I could update the stages reactively. Here is my full code; I used the push method of an array object and it works:

<template>
  <div>
    <li v-for="item in stages" :key="item.stageId">
      {{ item }}
    </li>
  </div>
</template>

<script>
export default {
  data() {
    return {
      stages: [],
    };
  },
  methods: {
    async getTeamStages() {
      this.stages = [{ stageId: 1 }, { stageId: 2 }];
      for (let stage of this.stages) {
        this.$set(stage, "tickets", []);
      }
      for (let stage of this.stages) {
        await this.getStageTickets(stage);
      }
    },
    async getStageTickets(stage) {
      const tickets = ["a", "b", "c"];
      for (let ticket of tickets) {
        this.stages[this.stages.indexOf(stage)].tickets.push(ticket);
      }
    },
  },
  mounted() {
    this.getTeamStages();
  },
};
</script>

It should be noted that I used the concat method of an array object and also works:

this.stages[this.stages.indexOf(stage)].tickets = this.stages[this.stages.indexOf(stage)].tickets.concat(tickets);

I tried your approaches some of them work correctly:

NOT WORKED

this.$set(this.stages[this.stages.indexOf(stage)].tickets, tickets)

WORKED

this.$set(this.stages[this.stages.indexOf(stage)].tickets, 0, tickets[0]);

WORKED

  stage.tickets = tickets
  this.stages.splice(this.stages.indexOf(stage), 1, stage)

I'm sure it is XY problem..

Upvotes: 3

Related Questions