4

I'm new to Meteor and the angular2-meteor package and I'm trying to learn how to use them following the well made "Socially" app tutorial found here: Socially turorial.

While experimenting with what I have seen there about the Publish/Subscribe functions, I've found a problem which I don't know how to solve. To make it clear I've made a very simple project with this structure:

/typings/test.d.ts

interface Test {    
    _id?: string;
    num: number;
}

/collections/tests.ts

export var Tests = new Mongo.Collection<Test>('tests');

/server/main.ts

import './tests';

/server/test.ts - is just publishing everything

import {Tests} from 'collections/tests'; 

Meteor.publish('tests', function() {
    return Tests.find();
});

/client/app.ts - subscribe sorting the results

import {Component, View} from 'angular2/core';

import {bootstrap} from 'angular2-meteor';

import {MeteorComponent} from 'angular2-meteor';

import {Tests} from 'collections/tests';

@Component({
    selector: 'app'
})

@View({
    templateUrl: 'client/app.html'
})

class Socially extends MeteorComponent {
    tests: Mongo.Cursor<Test>;

    constructor(){
        super();      
        this.subscribe('tests', () => {
            this.tests = Tests.find({}, {sort: {num: -1}});
        }, true);
    }
}

bootstrap(Socially);

/client/app.html - a simple ngFor, showing the results

<div>
    <ul>
        <li *ngFor="#test of tests">
            <p>{{test._id}}</p>
            <p>{{test.num}}</p>
        </li>
    </ul>
</div>

If I insert or remove entries from the database using the Mongo console, everything is working fine. The same thing if I update an existing entry without changing their order. However, if I try to update one of the already existing entries putting a higher number in its "num" field making them switch positions, the app stops working as it should and the browser console says:

EXCEPTION: TypeError: l_test0 is undefined in [{{test._id}} in Socially@3:15]

For example if I have :

  • id: 1 - num: 6

  • id: 2 - num: 5

  • id: 3 - num: 4

and then I try to update the entry with "num" 4 putting "num" to 7 instead, the new results should be:

  • id: 3 - num: 7
  • id: 1 - num: 6
  • id: 2 - num: 5

which is giving me the said error.

It might be stupid, but since I'm a real newbie with meteor I can't seem to understand what's wrong and I would be glad if you could give me a hand.

Thank you in advance.

EDIT: In my original project I used a form to add/remove entries directly from the page. In this test project I removed all the unnecessary things to keep it as simple as possible and used the mongo console:

db.tests.insert({_id: 1, num: 6})

db.tests.insert({_id: 2, num: 5})

db.tests.insert({_id: 3, num: 4})

db.tests.update({_id: 3}, {$set: {num: 7}})

If I then do:

db.tests.find()

it shows:

{ "_id" : 1, "num" : 6 }

{ "_id" : 2, "num" : 5 }

{ "_id" : 3, "num" : 7 }

GiuZu
  • 98
  • 6
  • For testing purposes, could you try this syntax: `{{test?._id}}` and `{{test?.num}}` in your template (questionmark). Perhaps it is a bug in angular2, and this is a work-around – Poul Kruijt Jan 15 '16 at 22:04
  • @PierreDuc thanks for your suggestion. I tried to do as you said and the error is obviously gone, but now the last updated entry just disappear until I reload the page again ( the "id: 3 - num: 7" in my last example). – GiuZu Jan 15 '16 at 22:46
  • Mmm, where do you update the num value? In js or in mongo? And if in js, how? – Poul Kruijt Jan 16 '16 at 00:02
  • @PierreDuc I edited my original message to answer your question. – GiuZu Jan 16 '16 at 07:28

2 Answers2

2

UPDATE: Check here, the bug is fixed since angular2-meteor@0.5.1! You can use Mongo.Cursor now.

The problem is that when using sort and the list changes, you cannot use tests:Mongo.Cursor<Test> with *ngFor right now. *ngFor does not support Mongo.Cursor perfectly in angular2-meteor. So you need use pure Angular 2 way.

You need fetch() first and use Array<Test>, otherwise it will show this error when the list or order of list changes:

Cannot read property 'XXX' of undefined

So the final working code is like this:

<div *ngFor="#test of tests">

</div>

// here you cannot use tests:Mongo.Cursor<Test>
tests:Array<Test>;
constructor() {
    super();
}
ngOnInit()
{
    this.subscribe('tests', () => {
        // autorun makes sure it get latest data
        this.autorun(() => {
            // here you need fetch first
            this.tests = Tests.find({}, {sort: {num: -1}}).fetch();
        }, true);
    });
}

Refer to issue on github

Hongbo Miao
  • 45,290
  • 60
  • 174
  • 267
  • Now that I'm able to comment, moving this here: Can't you just use the autobind argument to autorun to run the callback in an Angular change detection zone like so: `this.autorun(() => { this.tests = Tests.find({}, {sort: {num: -1}}).fetch(); }, true);` – Trygve Mar 28 '16 at 13:48
  • @Trygve That line just makes sure your executing work runs inside the Angular zone to let UI updates automatically. For me, if I don't add that line, my UI will not update unless me click that part. – Hongbo Miao Mar 28 '16 at 14:43
  • Right, but unless I'm misunderstanding you, I believe the second argument to `this.autorun` does the same thing. [Reference here](http://www.angular-meteor.com/tutorials/socially/angular2/privacy-and-publish-subscribe-functions) (Search for 'autobind'). – Trygve Mar 28 '16 at 15:20
  • @Trygve I made a reproduction. And I just tested, they are different. The code is [here](https://github.com/Hongbo-Miao/ngFor-issue-angular2-meteor). Now it is using `Mongo.Cursor`. Run `meteor`, use Robomongo or similar apps to open the database, you will see two items in collection `tests`. Manually delete one, the UI will update automatically, when you delete the second item, you will see the error this question said. Now change to `Array`, still manually delete one item, if you do not use `ngZone`, the list won’t update automatically. – Hongbo Miao Mar 28 '16 at 15:40
  • @Trygve my understanding is that `this.autorun` is to make sure subscribe latest data from server. It is a wrapper for meteor `Tracker.autorun`. While `NgZone` is from Angular 2, which makes sure the UI update. – Hongbo Miao Mar 29 '16 at 04:26
  • I haven't had a chance to test your reproduction, but I don't doubt that something's going wrong here. I do think it's an error in angular2-meteor though. Take a look at the [meteor_component code here](https://github.com/Urigo/angular2-meteor/blob/master/modules/meteor_component.ts): `this._bindToNgZone(func)` – Trygve Mar 29 '16 at 13:24
  • @Trygve I think you are right! Wow, I just realized both angular 2 and meteor uses same NgZone. Check [here](https://github.com/Urigo/angular2-meteor/issues/170),the bug is fixed!! I think you can create another issue maybe because they are different issues. – Hongbo Miao Mar 29 '16 at 15:37
  • @Trygve I updated my answer, now no need to use NgZone anymore. Just add a `true` for ` this.autorun `! – Hongbo Miao Apr 25 '16 at 00:51
1

I'm hoping there is a better answer out there, but I have the same problem and have been trying a few things.

I've found that if I add an observer to the cursor inside an autorun, then everything behaves as expected:

this.tests.observeChanges({changed: () => {
    this.tests = Tests.find({}, {sort: {num: -1}});
}})

Based on everything I've been reading, it doesn't seem like this should be necessary, but it does work for me.

To prevent it from trying to display the document in the cursor that briefly ends up undefined, I added an ngIf:

<li *ngFor="#test of tests" *ngIf="test">

Edit

I've been having some issues with this that were hard to pin down. After reading this issue on ngFor and ngIf, I'm guessing it's because I have ngFor and ngIf on the same element, which apparently isn't supported. Instead, they recommend moving the ngIf up to an enclosing <template> tag:

<template *ngIf="tests">
    <li *ngFor="#test of tests">
</template>

However, this reveals what may be the root issue, which Hongbo's answer addresses.

Trygve
  • 591
  • 1
  • 7
  • 22