0

The scenario is as follows...

Component template

<template>
  <div>
   <loader v-show="loading"></loader> // loading animation
   <div v-show="!loading">
     <div v-for="group in groups">
       {{group.name}}
       <div v-for="item in group.list">
         {{item.name}}
       </div>
     </div>
   </div>
  </div>
</template>

Component data

data: function () {
    return {
        list: [],
        groups: [],
        loading: true
    }
}

1. fetch 1 dimensional array from api

axios.get(API_URL).then(
    (response) => {
        this.list = response.data.payload;
    }
);

The array structure is as follows...

[
  {
    "name": "bob",
    "group": "A"
  },
  {
    "name": "sally",
    "group": "A"
  },
  {
    "name": "john",
    "group": "B"
  },
  {
    "name": "jane",
    "group": "B"
  },
]

2. transform array into 2 dimensions using the group property of each item

current solution (blocking!, inefficient?)

// loading animation stops at this point
this.list.forEach((item, index) => {
    let hasGroupChanged = false;
    if (index === 0) {
        hasGroupChanged = true;
    } else {
        let currentGroupName = item.group;
        let previousGroupName = this.list[index - 1].group;
        hasGroupChanged = previousGroupName !== currentGroupName;
    }
    if (hasGroupChanged) {
        const group = {
            group: item.group,
            list: []
        };
        this.groups.push(group);
    }
    const groupIndex = this.groups.length - 1;
    this.groups[groupIndex].list.push(item);
});

this.loading = false;

how to keep the loading animation going until groups have been populated?

Gabriel Stein
  • 428
  • 1
  • 4
  • 22
  • Do you want a way to populate arrays efficiently or do you want to ensure the loading animation runs till you've loaded the data? Does your current solution keep the loading animation going till the end? What makes your current solution blocking? – Andre Nuechter Jan 05 '20 at 13:28

3 Answers3

1

Your "loading" animation is "frozen" because JavaScript is single-threaded and when your transformation code is running (assuming there are much more data than shown in the example so it is running for significant amount of time), browser's rendering is blocked.

Either you can optimize the transformation code to make it faster or you can take a look at this SO answer for details and solutions how to make long running operations not to block browser's rendering....

Michal Levý
  • 33,064
  • 4
  • 68
  • 86
0

I guess the problem is that you set loading to false before axios is done getting your data (because that is the only async operation in the code).

The .forEach could probably be optimized a bit, but I doubt that is the culprit.

Try moving all your code in the .then chained off of the axios.get() instead.

forkerino
  • 123
  • 9
0

I can't really speak to the efficiency of your code, but a simple way to keep a loading indicator going until after an async operation finishes, is to to stop it in a finally-block:

const btn = document.getElementById('btn');
const mockLoadIndicator = document.getElementById('loading');
const data = [
  {
    "name": "bob",
    "group": "A"
  }, {
    "name": "sally",
    "group": "A"
  }, {
    "name": "john",
    "group": "B"
  }, {
    "name": "jane",
    "group": "B"
  }
];
const mockAPI = () => {
  mockLoadIndicator.style.display = 'block';
  return new Promise((resolve) => {
    setTimeout(() => resolve(data), 1000);
  });
};

btn.onclick = () => mockAPI()
.then(console.log)
.finally(() => {
  mockLoadIndicator.style.display = 'none';
});
#loading {
  position: relative;
  display: none;
  height: 10px;
  width: 10px;
  border-radius: 50%;
  background: grey;
  animation-duration: 300ms;
  animation-name: load;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

@keyframes load {
  from {
    left: 10px;
  }
  to {
    left: 40px;
  }
}
<button id="btn">Get Data</button>
<div id="loading"></div>
Andre Nuechter
  • 2,141
  • 11
  • 19