Q:

Update array items asynchronously - watches not firing

I have created a component that displays blog article previews. This component has pagination and upon selecting a new page I refresh the array of article previews. The list of articles is fetched from a JSON api from server1. The response contains information to fetch each article from server 2. Then I fire x asynchronous fetches to server 2, as many as items in the first response. In those responses I update the items in the array.
I am new to vue but after some struggling I got this to work. Now I'm trying to add a spinner in the article previews while the separate articles are loading. My idea was to watch in the previewcomponent for an article update and show the spinner depending on that. Unfortunately it doesn't work and now I'm starting to doubt my implementation. I notice that the watch in the preview is not called for every previewcomponent but still every preview is updated and shown correctly. I assume this is because of the messaging system but I don't manage to fix it.
My question is twofold:

Is my implementation a correct way of handling this problem? To get this to work I nicely I need to 'erase' the array because otherwise new articles were 'overwriting' old ones and this was visible.
How can I handle the spinners. Why are the watches not triggered and how can I fix this? In the code below I have some console writes. I see 10 times 'async' and each time a different amount of 'watch', never 10.

The complete code is on github here: Home and ArticlePreview. These are the most relevant parts:
Home:
<template>
    <div class="container article">
        <div class="row" v-for="(article, index) in articles" :key="index">
            <ArticlePreview v-bind:blogEntry="article"></ArticlePreview>
        </div>
        <b-pagination-nav :use-router="true" :link-gen="generateLink" align="center" :number-of-pages="nofPages" v-model="pageIndex" />
    </div>
</template>

data: function ()
{
    return {
        articles: <BlogEntry[]> [],
        nofPages: 1
    }
},

loadContent()
{
    fetch("./api/v1/articles.php?page=" + this.pageIndex)
    .then(response => response.json())
    .then((data) =>
    {
        this.nofPages = Math.ceil(data.nofItems/10);
        this.articles.splice(0);
        this.articles.splice(data.data.length);
        let index :number;
        for(index = 0; index < data.data.length; index++)
        {
            createArticleAsync(data.data[index].name, data.data[index].permlink).then(function(this: any, index: number, article: BlogEntry)
            {
                console.log('async');
                    Vue.set(this.articles, index, article);
            }.bind(this, index));
        }
    })
},

ArticlePreview:
<template>
    <div class="container-fluid">
        <div class="row" v-if="blogEntry">
            <template v-if="blogEntry">
                <div class="imageframe col-md-3">
                    <div class="blog-image">
                        <img :src="blogEntry.previewImage" style="border-radius: 5px;">
                    </div>
                </div>
                <div class="col-md-9">
                    <h5 class="font-weight-bold" style="margin-top:5px;"><router-link :to="{ name: 'Article', params: {author: blogEntry.author, permlink: blogEntry.permlink } }">{{blogEntry.title}}</router-link></h5>
                    <div class="multiline-ellipsis">
                        <p>{{blogEntry.previewBody}}</p>
                    </div>
                    <span class="metadata"><i>by <a :href="AuthorBlogLink">{{blogEntry.author}}</a> on {{blogEntry.created | formatDate}}</i></span>
                </div>
            </template>
            <template v-else>
                <p>Loading</p>
            </template>
        </div>
    </div>
</template>

<script lang="ts">
    import Vue from "vue";
    import VueRouter from 'vue-router';

    import {formatDate} from "../utils/utils";

    export default Vue.extend({
        props: [
            'blogEntry'
        ],
        data: function ()
        {
            return {
                loading: true
            }
        },
        watch:
        {
            blogEntry(newValue)
            {
                console.log('watch');
                if(newValue)
                    this.loading = false;
                else
                    this.loading = true;
            }
        }
    });
</script>

A:

I think the method of getting the detailed data of the article should be encapsulated inside the component, and the loading state is also maintained internally.just like the code below:(It doesn't work properly because Mockjs cannot execute correctly in snippet)

Mock.setup({timeout: 2000})
const URL_ARTICLE_LIST = '/api/v1/articles.php'
const URL_ARTICLE_DETAIL = '/api/v1/article_detail.php'

Mock.mock(/\/api\/v1\/articles\.php.*/,function(options){
  return {
    nofItems: 33,
    data: Mock.mock({
      'list|10': [{
        'title': '@title',
        'url': URL_ARTICLE_DETAIL
      }]
    }).list
  }
})
Mock.mock(URL_ARTICLE_DETAIL,function(options){
  return Mock.mock({
   content: '@paragraph'
  })
})

Vue.component('article-card',{
  template:  `
    <div>
      <template v-if="!loading">
        <div class="article-title">{{articleTitle}}</div>
        <div class="article-content">{{article.content}}</div>
      </template>
      <template v-else>
        <div>loading...</div>
      </template>
    </div>`,
  data () {
    return {
      loading: false,
      article: {}
    }
  },
  props: {
    articleTitle: {
      required: true,
      type: String
    },
    articleUrl: {
      required: true,
      type: String
    }
  },
  watch: {
    articleUrl (url,oldUrl) {
      if(url && url!=oldUrl){
       this.loadContent()
      }
    }
  },
  methods: {
   loadContent () {
    this.loading = true;
      //or your own async functions
      axios.get(this.articleUrl).then(res=>{
        this.article = res.data
        this.loading = false;
      })
   }
  },
  created () {
   this.loadContent()
  }
});

var app = new Vue({
  el: '#app',
  data () {
   return {
     articles: [],
     nofPages: 1,
     page: 1 //you should get page param from vue-router just like this.$route.query.page    
   }
  },
  created () {
    //you can also use fetch here
    axios.get(URL_ARTICLE_LIST+'?page='+this.page).then(res=>{
     console.log(res.data)
      this.nofPages = Math.ceil(res.data.nofItems/10);
      this.articles = res.data.data
    })
  }  
})
 ul,li{
  list-style: none;
 }
 .article_list{
  display: flex;
  flex-wrap: wrap;
 }
 .article_list>li{
  width: 300px;
  background: skyblue;
  color: white;
  margin: 10px;
 }
 .article-content{
  text-indent: 2em;
 }
 .pagination-wrapper>li{
  display:inline-block;
  padding: 5px;
  border: 1px solid skyblue;
  margin: 3px;
 }
 .pagination-wrapper>li.active{
  background: skyblue;
  color: #fff;
 }
<script src="https://cdn.bootcss.com/Mock.js/1.0.1-beta3/mock-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
  <ul class="article_list">
    <li v-for="article of articles">
      <article-card :article-title="article.title" :article-url="article.url"></article-card>
    </li>
  </ul>
  <ul class="pagination-wrapper">
   <li v-for="x in nofPages" :class="{active: page==x}">{{x}}</li>
  </ul>
</div>

