Following the Angular 2 - Meteor instructions for pagination gives me this error when I try to change the page (pageSize
is less than totalItems
):
Exception in queued task: EXCEPTION: Error in client/components/entities/players/player-list.html:2:8
ORIGINAL EXCEPTION: TypeError: Cannot read property 'type' of undefined
ORIGINAL STACKTRACE:
TypeError: Cannot read property 'type' of undefined
at AppElement.detachView (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:11331:17)
at ViewContainerRef_.remove (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:11531:34)
at NgFor._bulkRemove (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:21719:37)
at NgFor._applyChanges (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:21684:33)
at NgFor.ngDoCheck (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:21670:22)
at DebugAppView._View_PlayerList0.detectChangesInternal (PlayerList.template.js:94:41)
at DebugAppView.AppView.detectChanges (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:12703:14)
at DebugAppView.detectChanges (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:12808:44)
at DebugAppView.AppView.detectViewChildrenChanges (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:12729:19)
at DebugAppView._View_PlayersPage0.detectChangesInternal (PlayersPage.template.js:395:8)
ERROR CONTEXT:
[object Object]
9debug.js:41 Exception in queued task: TypeError: Cannot read property 'splice' of null
at MongoCursorObserver._removeAt (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:77671:19)
at removedAt (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:77649:35)
at http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:77364:20
at ZoneDelegate.invoke (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:124251:29)
at Zone.run (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:124144:44)
at Object.removedAt (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:77363:23)
at removed (http://localhost:3000/packages/minimongo.js?hash=88217d643bc16fdf3505c6d4b2b8f5ddc400c49a:3745:28)
at self.applyChange.removed (http://localhost:3000/packages/minimongo.js?hash=88217d643bc16fdf3505c6d4b2b8f5ddc400c49a:3674:44)
at http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:77364:20
at ZoneDelegate.invoke (http://localhost:3000/packages/modules.js?hash=fa01f730b3659d348eabf8ba338dffb7d96b4033:124251:29)
My files are a simplified version of their tutorial:
import { Component } from '@angular/core';
import { Players } from '../../../../collections/players';
import { Mongo } from 'meteor/mongo';
import { MeteorComponent } from 'angular2-meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { PaginationService, PaginatePipe, PaginationControlsCmp } from 'angular2-pagination';
@Component({
selector: 'player-list',
viewProviders: [PaginationService],
templateUrl: 'client/components/entities/players/player-list.html',
directives: [PaginationControlsCmp],
pipes: [PaginatePipe]
})
export class PlayerList extends MeteorComponent{
players: Mongo.Cursor<Party>;
pageSize: number = 5;
curPage: ReactiveVar<number> = new ReactiveVar<number>(1);
nameOrder: number = 1;
constructor() {
super();
this.autorun(() => {
let options = {
limit: this.pageSize,
skip: (this.curPage.get() - 1) * this.pageSize,
sort: { name: this.nameOrder }
};
this.subscribe('players', options, () => {
this.players = Players.find({}, { sort: { name: this.nameOrder } });
}, true);
});
}
onPageChanged(page: number) {
this.curPage.set(page);
}
}
And the relevant HTML:
<div>
<ul>
<li *ngFor="let player of players | paginate:{currentPage: 1, itemsPerPage: pageSize, totalItems: 14}">
<p>{{player.name}}</p>
</li>
</ul>
<pagination-controls (change)="onPageChanged($event.page)"></pagination-controls>
</div>
I have checked in the publish
function to verify that the correct number of documents are being returned each time. (using .fetch().length
to correctly measure the impact of the limit: 10
)
What I've tried I've changed:
this.players = Players.find({}, { sort: { name: this.nameOrder } });
to:
this.players = Players.find({}, { sort: { name: this.nameOrder } }).fetch();
which prevents the errors above from coming up when changing the page on pagination.
This creates a new (non-breaking) error where length
of the collection goes from 5 to 10 after switching pages -- switching pages again seems to have it stay at 10. As if the subscription is remembering the documents from the last subscription call even though publish
logs show the correct number of documents to return.
Questions
Should ngFor
work with cursors? If so, what could be causing the errors to occur?
Is there a reason why the subscription is remembering the previous documents? I tried storing the subscription and calling stop()
but that caused a glitch in the interface.
Should ngFor work with cursors? If so, what could be causing the errors to occur?
Yes, it works, however it is buggy. I created this issue before. But now it is still buggy sometimes. It happens when you use sort
.
The reason seems because when you use Mongo.Cursor
with sort
, and when the order of those items changes, it cannot trigger Angular 2's Change Detection correctly. And you can see it from the error you posted in the question.
And feel free to create an issue on github with a reproduction.
If you have app state using ngrx or you have your own app state, usually we save Array
inside of the app state instead of Mongo.Cursor
. And Array in app state helps us track and find the issue, because we always know what is in array.
(A special case, when you have large number of data, maybe thousands of items, you need use Mongo.Cursor
avoid using Array
because fetch()
needs time)
A good news is that new API which return Observable will be released soon. No more Cursor, track here: https://github.com/Urigo/angular2-meteor/pull/358
Is there a reason why the subscription is remembering the previous documents?
Check my presentation Desugar Meteor, Angular 2, RxJS 5, and ngrx page 34:
This is how Meteor works. If you do not stop the subscription, and try to subscribe another time (NOTE same Collection, in your case, Players
Collection), the old data will be still in blue rectangle (in Minmongo). Meteor will add new data in the blue rectangle(Players
Collection), so the blue rectangle will become bigger and bigger).
Two ways:
1) the first way you already tried. Stop the subscription. And subscribe another time. This way you will clean the Players
Collection in Minmongo. But not a really good way. It cause unnecessary stop
and resubscribe
. It cost time, making your page flash.
2) adding search conditions:
Players.find({
// add your search conditions here, limit to the data you need
}, { sort: { name: this.nameOrder } })
In the presentation page 34, this means although you have a lot of data in the Minmongo. But you only need yellow rectangle part of data.