Remove elements on client-side only collection


#1

I’m using a client-side only collection to which I publish from the server some data that is coming from an API call.

This collection needs to be client-only as it will be used to make further API calls.

I’m trying to clean this collection (to then subscribe to a different set of data) by running this code

Reports.find({}).forEach(report => {
    Reports.remove(report._id)
})

and I get the following error remove failed: Method not found. It seems to be telling me to use the server-side Meteor.method to remove it, but as it is a client-only collection I am unable to call it from the server.

Here’re some relevant code blocks so you may understand this better:

// client
Reports = new Mongo.Collection('reports')
// server
Meteor.publish("reports", function({page = 1}) {

    let reports = getReportsFromAPI() // returns an array

    reports.forEach(row => {
        let report = {
            // map data to my model
        }
        this.added('reports', Random.id(), report)
    })

    this.ready();
})

Any best practices you recommend to deal with external data that comes from an API? I need to manage it from the client and make subsequent calls to filter and sort the data.


#2

@p4bloch this will solve your problems:

Reports._collection.find({}).forEach(report => {
    Reports._collection.remove(report._id)
})

Yup, that’s the client only collection at ._collection. You don’t have a collection defined on the server, so therefore the isomorphic version of remove tacked on to Reports–which is automatically trying to call the server–has no server remove method to call.

Reports._collection.remove({}) will also work–i.e. you can remove an entire client side collection like you couldn’t a server a collection–from the client.

Also note: transforms won’t be applied in Reports._collection.find().fetch(). You will have pass a transform property in the options like so:

Reports._collection.find({}, {
   transform: function(obj) {
       obj.className = 'Report'; //just an example of some work u can do here 
       return obj;
   }
}).fetch()

#3

wow, thank you so much for this tip. I had no idea such a thing existed and had a hard time trying to google that out.

Using Reports._collection.remove({}) worked perfectly :smile:


#4

I wonder what the official approach on this issue is, though. I’ve seen that using client-only collections is encouraged, but having this method undocumented makes me wonder what would be a better way to accomplish this task.

What do you think is the best way to query an external API and publish that data to the client, and then use some new data when filtering or paginating?


#5

The only safe way I know is to use 2 collections:

  • a client-server one, used to send data from server to client,
  • a client-only one (also called an “unmanaged” collection), used to mirror and/or modified the first collection

You create a client-only collection by passing null to the collection constructor (see here).
Your mirror the collection by using an observer (see here).


#6

btw when using this.added, does it update data if something changed, or it does not compare and you would really need this.changed ? during resubscribe

and I read about few people using method results to fill local only collection.


#7

calling added again will non-fatally fail client-side. You need to maintain your own “state” server-side to know whether to use changed or added. I don’t see why added couldn’t just merge them client-side if the docs are exactly the same. There’s probably an implementation reason for this.


#8

I wouldn’t worry about it. I’ve been using it forever. I’m pretty sure it’s here to stay.

Not sure what you mean about “new data”, but basically the following:

would only be needed if you need to keep a record of some “state” server side, i.e. total docs in the collection for pagination, and specifically if you need to keep that number up to date, in which case you need to know the previous doc count, which you’d store on the server. Though something that simplistic you could just store on namespaced global variable, rather than a server-side collection. But you get the idea: when you need to know the previous state in addition to the new state, server side storage of sorts will come into play. Once the previous state you need to know is essentially arrays of objects, well then server-side collections become your best option.

…If, however, for example, you were just pinging a 3rd party API to get data, say to get tweets, you wouldn’t need any server side (“server-client”) collection. You could just ping the API, get the data, and send it to the client’s client-only collection via added. It’s a great approach. A very professional one. Cuz now you aren’t unnecessarily maintaining a server side collection. The less professional approach for this pattern is to use Meteor.Methods to get the tweets, but then you can’t easily observe them. It’s super nice to be able to use cursors from fake client-only collections in your Spacebars templates, and know they will always be up to date every time you call added or changed.

In short, using client-only collections and manually managed publications is super pro. Nice work!


Difference between new Mongo.Collection(null) on client and new Mongo.Collection('things') on client?
Storing API/Method result in temporary local collection? Using komposer
#9

I see, thanks for the hint.


#10

Thank you for taking the time to make such an elaborated answer, @faceyspacey!

Right, in my scenario, having data in the server is conceptually wrong, because the app is a visualization (tables, map) of data that comes entirely from an external API. So, if the data is saved in the server, it would be saved with the current state (page 2 of the dataset, for example). Not what I need.

I think I don’t even need a global variable because I’m sending all the relevant data to make the correct API call through the publication, and that’s all the info the server ever receives of the state of the client side collection.

// on the client helper, when pagination is clicked
Meteor.subscribe('reports', {
    page: 2,
    filters: ['filter1', 'filter2']
}, {
    onReady: function() {
        // update client
    }
});