skip to Main Content

I have a news page which is being loaded by Vue and Vue Isotope. Vue isotope requires that my data is an array so that I can append more articles to the isotope when the user clicks the “Load more” button. The way this works is, when the user clicks “load more”, using Laravel’s pagination function, I can simply append the next load of articles to my existing array of articles in my Vue instance.

The issue I am having is that my Laravel collection always returns an object instead of an array, even when I use toArray().

If I load my articles straight from eloquent using get(), this works fine and is an array, however because I need to merge multiple eloquent queries together, I am forced to use a collection and merge() to merge 4 eloquent queries into one large collection. This is what I believe is forcing the collection to always return as an object.

Below is my function from the controller:

public function apiIndex()
{
    $articles = Article::latest('featuredArticle')->latest()->published()->get();
    $twitterPosts = TwitterPost::where('published', 'published')->get();
    $facebookPosts = FacebookPost::where('published', 'published')->get();
    $vimeoPosts = VimeoPost::where('published', 'published')->get();

    $articles->map(function ($post) {
        $post['type'] = 'Article';
        return $post;
    });

    $twitterPosts->map(function ($post) {
        $post['type'] = 'Twitter';
        return $post;
    });

    $facebookPosts->map(function ($post) {
        $post['type'] = 'Facebook';
        return $post;
    });

    $vimeoPosts->map(function ($post) {
        $post['type'] = 'Vimeo';
        return $post;
    });

    $allPosts = collect();

    $allPosts = $allPosts->merge($articles);
    $allPosts = $allPosts->merge($twitterPosts);
    $allPosts = $allPosts->merge($facebookPosts);
    $allPosts = $allPosts->merge($vimeoPosts);

    $allPosts = $allPosts->sortByDesc('created_at')->paginate(15);

    return response()->json($allPosts);

}

The closest I have been able to get is by adding ->values() to the end of $allPosts however this removes the pagination link for the next page and only retrieves the actual posts so I cannot use this.

Here is a small snippet for retrieving the initial $allPosts data into Vue

    mounted () {
        axios.get(this.page)
        .then(response => ([
            this.list = response.data.data,
            this.page = response.data.next_page_url
        ]))
    },

Here is a snippet of what method is called when the user clicks the “Load more” button

        loadMore: function() {
            axios.get(this.page)
            .then(response => ([
                this.paginatedList = response.data.data,
                this.list.push(this.list, this.paginatedList),
                this.page = response.data.next_page_url
            ]))
        },

Because the response is an object, using this.list.push() does not work and throws an error because this.list should be an array.

Additionally, here is a snippet of my Vue data object

        data: {
            page: "{{route('api-news')}}",
            list: [ 

            ],
            paginatedList: [

            ],
},

Image of my list object (where list should be an array instead):

enter image description here

How would I be able to force $allPosts to return as an array?

3

Answers


  1. I would try adding a call to values() before the paginate() instead of after.

    $allPosts = $allPosts->sortByDesc('created_at')->values()->paginate(15);
    return response()->json($allPosts);
    

    The way Vue determines whether an array is treated as an array vs an object is by looking at the keys: if they are numeric, zero-indexed and sequential, it will treat it an array, otherwise, it treats it as an object.

    In your case the sortByDesc() collection method maintains the original keys so Vue sees non-sequential keys and handles it as an object. By running your sorted collection through values() you should get a correctly indexed collection to pass to Vue.

    Update:

    Based on your comment that the above solution is only working for the first page of results but not the others, I suspect that the paginate() method is also maintaining the original keys. So the first chunk has keys starting at 0, but the second chunk has keys starting at 15. You may need to override that so that each chunk gets re-indexed to 0.

    You may also be able to do this in your Vue code before pushing the results into your model. Something like response.data.list.filter(item => item) might work.

    Login or Signup to reply.
  2. Coming across this issue as well, as @Graeme states, this works on the first page, but not the second. In the end I ended up manually overriding the items in the LengthAwarePaginator and resetting the array indexes.

            $collect = $Products->get()->values();
            
            $ProductsPaginated = ProductResource::collection($collect)->paginate(12);
            $items = $ProductsPaginated->items();
            
            $decodeFix = json_decode($ProductsPaginated->toJson());
            $decodeFix->data = array_values($items);
    
            return json_encode($decodeFix);
    
    Login or Signup to reply.
  3. Another way:

    $products = Product::where(...)->get()->paginate(25);
    $products->setCollection($products->getCollection()->values());
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search