2

I've got one view displaying some pictures published by users with some data (let's image Instagram).

I already have these pictures as non-reactive data (otherwise you could see many updates) but these images have one button to like the picture. If I have this as non-reactive data I can't see when I click on "Like" the filled heart (I need to refresh).

This is my subscribe function:

this.subscribe('food', () => [{
            limit: parseInt(this.getReactively('perPage')),
            //skip: parseInt((this.getReactively('page') - 1) * this.perPage),
            sort: this.getReactively('sort')
        }, this.getReactively('filters'), this.getReactively('searchText'), this.getReactively('user.following')
        ]);

And this is my helper:

food() {

const food = Food.find({}, {reactive: true}, {
                        sort: this.sort
}).fetch().map(food => {
const owner = Meteor.users.findOne(food.owner, {fields: {username: 1, avatarS: 1, following: 1}});
                        food.avatarS = owner && owner.avatarS;
                        food.username = owner && owner.username;

                        if (food.likes.indexOf(Meteor.userId()) == -1) {
                            // user did not like this plate
                            food.liked = false;
                        } else {
                            // user liked this plate
                            food.liked = true;
                        }

                        return food;
                    });
}

Is possible to have a non-reactive model but with some reactive properties on it?

I'm using Angular 1.X with TS btw

Thanks in advance!

PS: is it normal that this works as non-reactive when I change reactive to true?

Dani
  • 3,128
  • 2
  • 43
  • 91

2 Answers2

2

Modification to your code:

//console.log(food.likes);
this.subscribe('reactiveFoodData', {ownerId: food.owner, userId: Meteor.userId()}).subscribe(()=>{
    console.log(this.user);
});

// THIS IS THE PUBLISH METHOD LOCATED IN THE SERVER SIDE:
 Meteor.publish('reactiveFoodData', function(params: {ownerId:string, userId:string) {
  const owner = Meteor.users.findOne(params.ownerId);

  if (!owner) {
    throw new Meteor.Error('404', 'Owner does not exist');
  }

  let result = {};
  result.avatarS = owner.avatarS;
  result.username = owner.username;

  const food = Food.find({});
  result.liked = !(food.likes.indexOf(params.userId) == -1);
  return result;
}); 

You have few problems: 1. The reactive flag is true by default, you do not need to set it. 2. The function find is accepting only two arguments, not 3.

Should be:

const food = Food.find({}, {reactive: true, sort: this.sort})

If you need some, subset of data to be reactive only (from some collection). You could create a specific Method (which udpates only "likes"). https://guide.meteor.com/methods.html

UPDATE: Here is how you write a method with return parameter (check two examples, with Future and without): How to invoke a function in Meteor.methods and return the value

UPDATE2:

  1. You have lost reactivity when you used fetch(). Because you moved from reactive cursor to just simple array over which you map values. Do not expect reactivity after fetch(). If you want fetch or do not want to use Cursors, you could wrap the find inside Tracker.autorun(()=>{}) or utilize publish/subscribe.

Note: But be careful, if you somehow manage to get "empty" cursor in find(), your Tracker.autorun will stop react reactively. Autorun works only if it has something to watch over.

  1. The main point with method, is that if you want to have one time non-reactive action for something. You define the method on server:

    Meteor.methods({
        myMethod: ()=> {
            return "hello";
        }
    });
    

And you can call it from client with:

    Meteor.call('myMethod', (error, result) => {
       console.log(result); // "hello"
    });
  1. Instead of working with pure collections. You could start using publish/subscribe. On server you publish 'likes' and on client you just listens to this new reactive view. E.g.,

    Meteor.publish('likes', (options: {owner: string, likes: Array<any>}) => {
        let result: any = {}
        const owner = Meteor.users.findOne(options.owner, username: 1, avatarS: 1, following: 1}});
        result.avatarS = options.owner && options.owner.avatarS;
        result.username = options.owner && options.owner.username;
        result.liked = !(options.likes.indexOf(Meteor.userId()) == -1)
        return result;
    });
    

On client side: Meteor.subscibe('likes', {food.owner, food.likes}).subscribe(()=>{});

Community
  • 1
  • 1
laser
  • 570
  • 4
  • 20
  • That's the weird part as I said. It's true by default but if I add it with a true value the data IS NOT reactive. But I think as a other user said, I should use Tracker.nonreactive(). About the 3 arguments you're right. Maybe I couldn't see anything because I wasn't sorted it, but nice one. About the method to update likes I should change the content in the first model. Not sure if I follow you with this. – Dani Feb 07 '17 at 11:35
  • @DanielRodriguez if you are talking about angular-meteor (as the tag suggests, then it means that you read their tutorial**) Do you use angular2 or angular1 with meteor? I followed the angular2 tutorial and it mentioned the method functions, that can be executed one time and return value. See for example, this https://github.com/Urigo/meteor-angular2.0-socially/blob/master/both/methods/parties.methods.ts ** some how it is missing from website, but can be still viewed at github. – laser Feb 07 '17 at 18:45
  • @DanielRodriguez check update. I think Tracker is overkill, what you want is to call server method and get a response from it – laser Feb 07 '17 at 19:50
  • I completed their tutorial yes, and I'm using Angular 1.X with TS (info updated) but since I finished it they changed and added many things (in fact that was one of my reports: https://github.com/Urigo/angular-meteor/issues/1369). Hmmm, not sure about the method. The user below commented the same but this method has to edit the previous model which is no reactive. You mean a method just to return the like and change in the ng-repeat for the initial model (e.g) model1.like (which is no reactive) to model2.like? – Dani Feb 07 '17 at 22:49
  • Interesting update :) Some points to comment: - You said that I'll lost the reactivity after .fetch(). If I remove the {reactive: true} on my find, event with the .fetch() it still reactive. Is that normal? - When you mention that Tracker will stop react reactively if cursor is empty you mean either that I have to "reactivate" it after add data to my cursor or in that moment won't work (which is understandable) I'll try this out later on when I come home. Cheers! – Dani Feb 08 '17 at 14:41
  • Looking now deeper into this I've got a similar code but with my food collection. I've got the publish and subscribe (updated question) method but I can't subscribe to likes because this is a property from Food (my collection). Maybe I'm not following you :S I also tried return Tracker.nonreactive(function() { return Food.find(); }); but as my find with reactive: true this still reactive as well (I was looking this other post: http://stackoverflow.com/questions/29197247/how-do-i-make-a-meteor-helper-non-reactive). Not sure what I'm doing wrong – Dani Feb 08 '17 at 22:20
  • @DanielRodriguez I think right now that publish/subscribe approach could be the best for you. As an example take a look into angular-meteor example project (from Urigo's repositories): a) First they on the server side publish uninvited users for parties: https://github.com/Urigo/meteor-angular2.0-socially/blob/master/server/imports/publications/users.ts b) Then they subscribe to 'uninvited' published list, on client https://github.com/Urigo/meteor-angular2.0-socially/blob/master/client/imports/app/parties/party-details.component.ts Everything is reactive. – laser Feb 08 '17 at 22:41
  • I started my project from that tutorial (socially) after finish it. The way to publish and subscribe is exactly the same I'm using, but again, I need to subscribe to Food, likes is just a property of this collection. That's the point: everything is reactive. Let's take that example: how could you remove the whole reactivity from Parties and have reactive just one property? – Dani Feb 08 '17 at 22:54
  • @DanielRodriguez that is what I am talking about. On the server, you find from Parties, by some key and return exactly that property. This property is reactive, but you do not fetch to the client the whole list of parties, only the property. In example it is a property "uninvited" for specific partyId. – laser Feb 09 '17 at 08:11
  • Ah ok ok, sorry. So I definitely can create publications from cursors and not only from collections. That has more sense now. I'll try later on. Thanks! – Dani Feb 09 '17 at 09:54
  • I've created a new publish method just to return this data from the user and I have the subscribe one into my helper but I've got this problem: argument 2 must be a function. Shall I put this subscribe into const food = Food.find({}).fetch().map(food => {}); ? Sorry, if I'm doing too many questions... – Dani Feb 09 '17 at 22:45
  • No, if you put inside Food.find({}).fetch().map(food => {}) then you will be fetching the whole collection on client, and this is not what you want. If the function does not accept two arguments, then give an object with as many properties-arguments as you want in the first argument. @DanielRodriguez **answer updated** – laser Feb 10 '17 at 08:06
  • But if I need to pass food.owner and food.likes these properties come from my helper. If I'm out of it, food contains the whole array with my different plates. I have uploaded my component here https://codeshare.io/2j0EPR (forget after foodCount()) – Dani Feb 10 '17 at 09:16
  • @DanielRodriguez normally you should not use operations on collections (as collections are updated with package insecure or autopublish). In the end of the day, everything should be controlled by publish/subscribe approach (for ddp) and methods for non reactive. On front end you receive some keys you want to use to restrict amount of received data, for example ownerId or food name and update it in your subscribe call, it will ask server to send only necessary subset of published data. – laser Feb 10 '17 at 20:39
  • And of course you do not need to send whole array of likes, as well as second parameter at all, as it is get using Meteor.userId, which is known at server (who is connected at the moment is known). – laser Feb 10 '17 at 20:40
  • I understand your second comment, in fact I do that. I don't send the whole collection of food: Meteor.publish('food', function(options, filters, searchString, following) {... But I don't get when you say that I can't do operations in my collection. What do you mean with that? The tutorial that I followed follows this way as well (subscription and helper with a cursor): https://github.com/Urigo/meteor-angular-socially/commit/315afd772f5e4791e11642df1ed0542300a3af42 – Dani Feb 10 '17 at 22:48
  • I'd like to understand your point of view because I consider this is quite important in a Meteor project – Dani Feb 10 '17 at 22:49
  • @DanielRodriguez In the tutorial read it step by step. First here https://github.com/Urigo/meteor-angular-socially/blob/master/manuals/views/step8.md about removing insecure, and then here https://github.com/Urigo/meteor-angular-socially/blob/master/manuals/views/step9.md about removing autopublish – laser Feb 10 '17 at 23:10
  • What you are actively using right now is autopublish - a meteor regime that should be removed for any real app... – laser Feb 10 '17 at 23:11
  • I'm not using autopublish, I removed this package at beginning. Have a look to the code that I shared with you again https://codeshare.io/2j0EPR and find this "// THIS IS THE PUBLISH..." I'm subscribed to my Food collection and I'm publishing just a few of them (the ones I want) – Dani Feb 11 '17 at 01:10
  • The package insecure was also removed. Again, I completed the Socially tutorial. You have to remove those packages on first steps – Dani Feb 11 '17 at 01:11
  • @DanielRodriguez updated, make it like that, but later remove Meteor.userId() from the call as server should already know the user Id connected and check that owner parameter is string. – laser Feb 11 '17 at 09:11
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/135453/discussion-between-daniel-rodriguez-and-laser). – Dani Feb 11 '17 at 15:32
1

This is just off the top of my head.

Have you tried looking at Tracker ? https://docs.meteor.com/api/tracker.html

But more specifically the method Tracker.nonreactive https://docs.meteor.com/api/tracker.html#Tracker-nonreactive

Jake Lacey
  • 623
  • 7
  • 24
  • interesting. Sounds like a new whole concept to me. I've seen this example: http://stackoverflow.com/questions/29197247/how-do-i-make-a-meteor-helper-non-reactive but I'm concern about this: "this function will not pay attention to any reactive data source updates in your own defined function". What about if I want some properties of that model reactives? – Dani Feb 07 '17 at 11:32
  • Sorry for the late reply... hmm good question maybe create a subscription specifically for the reactive data? – Jake Lacey Feb 08 '17 at 23:11
  • Yes, same approach than other user above. I'll try that later on. Cheers! – Dani Feb 09 '17 at 09:55
  • Do you have any example of that implementation? I use a helper to return a cursor with my data but I don't know where I have to define this subscription in order to use the properties from this cursor – Dani Feb 11 '17 at 01:14