In this step we are going to add:
- parties list pagination
- sorting by party name
- lastly, we will move our previously implemented parties location search to the server side.
Pagination simply means delivering and showing parties to the client on a page-by-page basis, where each page has a predefined number of items. Pagination reduces the number of documents to be transferred at one time thus decreasing load time. It also increases the usability of the user interface if there are too many documents in the storage.
Besides client-side logic, it usually includes querying a specific page of parties on the server side to deliver up to the client as well.
First off, we'll add pagination on the server side.
Thanks to the simplicity of the Mongo API combined with Meteor's power, we only need to execute Parties.find
on the server with some additional parameters. Keep in mind, with Meteor's isomorphic environment, we'll query Parties
on the client with the same parameters as on the server.
Collection.find
has a convenient second parameter called options
,
which takes an object for configuring collection querying.
To implement pagination we'll need to provide sort, limit, and skip fields as options
.
While limit and skip set boundaries on the result set, sort, at the same time, may not. We'll use sort to guarantee consistency of our pagination across page changes and page loads, since Mongo doesn't guarantee any order of documents if they are queried and not sorted. You can find more information about the find method in Mongo here.
Now, let's go to the parties
subscription in the server/imports/publications/parties.ts
file,
add the options
parameter to the subscription method, and then pass it to Parties.find
:
@@ -1,8 +1,12 @@
β 1β 1βimport { Meteor } from 'meteor/meteor';
β 2β 2βimport { Parties } from '../../../both/collections/parties.collection';
β 3β 3β
-β 4β βMeteor.publish('parties', function() {
-β 5β β return Parties.find(buildQuery.call(this));
+β β 4βinterface Options {
+β β 5β [key: string]: any;
+β β 6β}
+β β 7β
+β β 8βMeteor.publish('parties', function(options: Options) {
+β β 9β return Parties.find(buildQuery.call(this), options);
β 6β10β});
β 7β11β
β 8β12βMeteor.publish('party', function(partyId: string) {
On the client side, we are going to define three additional variables in the PartiesList
component which our pagination will depend on:
page size, current page number and name sort order.
Secondly, we'll create a special options object made up of these variables and pass it to the parties subscription:
@@ -8,6 +8,15 @@
β 8β 8β
β 9β 9βimport template from './parties-list.component.html';
β10β10β
+β β11βinterface Pagination {
+β β12β limit: number;
+β β13β skip: number;
+β β14β}
+β β15β
+β β16βinterface Options extends Pagination {
+β β17β [key: string]: any
+β β18β}
+β β19β
β11β20β@Component({
β12β21β selector: 'parties-list',
β13β22β template
@@ -15,10 +24,20 @@
β15β24βexport class PartiesListComponent implements OnInit, OnDestroy {
β16β25β parties: Observable<Party[]>;
β17β26β partiesSub: Subscription;
+β β27β pageSize: number = 10;
+β β28β curPage: number = 1;
+β β29β nameOrder: number = 1;
β18β30β
β19β31β ngOnInit() {
-β20β β this.parties = Parties.find({}).zone();
-β21β β this.partiesSub = MeteorObservable.subscribe('parties').subscribe();
+β β32β const options: Options = {
+β β33β limit: this.pageSize,
+β β34β skip: (this.curPage - 1) * this.pageSize,
+β β35β sort: { name: this.nameOrder }
+β β36β };
+β β37β
+β β38β this.partiesSub = MeteorObservable.subscribe('parties', options).subscribe(() => {
+β β39β this.parties = Parties.find({}).zone();
+β β40β });
β22β41β }
β23β42β
β24β43β removeParty(party: Party): void {
As was said before, we also need to query Parties
on the client side with same parameters and options as we used on the server, i.e., parameters and options we pass to the server side.
In reality, though, we don't need skip and limit options in this case, since the subscription result of the parties collection will always have a maximum page size of documents on the client.
So, we will only add sorting:
@@ -36,7 +36,11 @@
β36β36β };
β37β37β
β38β38β this.partiesSub = MeteorObservable.subscribe('parties', options).subscribe(() => {
-β39β β this.parties = Parties.find({}).zone();
+β β39β this.parties = Parties.find({}, {
+β β40β sort: {
+β β41β name: this.nameOrder
+β β42β }
+β β43β }).zone();
β40β44β });
β41β45β }
The idea behind Reactive variables and changes - is to update our Meteor subscription according to the user interaction - for example: if the user changes the sort order - we want to drop the old Meteor subscription and replace it with a new one that matches the new parameters.
Because we are using RxJS, we can create variables that are Observable
s - which means we can register to the changes notification - and act as required - in our case - changed the Meteor subscription.
In order to do so, we will use RxJS Subject
- which is an extension for Observable
.
A Subject
is a sort of bridge or proxy that is available in some implementations of RxJS that acts both as an observer and as an Observable.
Which means we can both register to the updates notifications and trigger the notification!
In our case - when the user changes the parameters of the Meteor subscription, we need to trigger the notification.
So let's do it. We will replace the regular variables with Subject
s, and in order to trigger the notification in the first time, we will execute next()
for the Subject
s:
@@ -1,5 +1,6 @@
β1β1βimport { Component, OnInit, OnDestroy } from '@angular/core';
β2β2βimport { Observable } from 'rxjs/Observable';
+β β3βimport { Subject } from 'rxjs/Subject';
β3β4βimport { Subscription } from 'rxjs/Subscription';
β4β5βimport { MeteorObservable } from 'meteor-rxjs';
β5β6β
@@ -24,9 +25,9 @@
β24β25βexport class PartiesListComponent implements OnInit, OnDestroy {
β25β26β parties: Observable<Party[]>;
β26β27β partiesSub: Subscription;
-β27β β pageSize: number = 10;
-β28β β curPage: number = 1;
-β29β β nameOrder: number = 1;
+β β28β pageSize: Subject<number> = new Subject<number>();
+β β29β curPage: Subject<number> = new Subject<number>();
+β β30β nameOrder: Subject<number> = new Subject<number>();
β30β31β
β31β32β ngOnInit() {
β32β33β const options: Options = {
@@ -42,6 +43,10 @@
β42β43β }
β43β44β }).zone();
β44β45β });
+β β46β
+β β47β this.pageSize.next(10);
+β β48β this.curPage.next(1);
+β β49β this.nameOrder.next(1);
β45β50β }
β46β51β
β47β52β removeParty(party: Party): void {
Now we need to register to those changes notifications.
Because we need to register to multiple notifications (page size, current page, sort), we need to use a special RxJS Operator called combineLatest
- which combines multiple Observable
s into one, and trigger a notification when one of them changes!
So let's use it and update the subscription:
@@ -4,6 +4,8 @@
β 4β 4βimport { Subscription } from 'rxjs/Subscription';
β 5β 5βimport { MeteorObservable } from 'meteor-rxjs';
β 6β 6β
+β β 7βimport 'rxjs/add/operator/combineLatest';
+β β 8β
β 7β 9βimport { Parties } from '../../../../both/collections/parties.collection';
β 8β10βimport { Party } from '../../../../both/models/party.model';
β 9β11β
@@ -28,20 +30,31 @@
β28β30β pageSize: Subject<number> = new Subject<number>();
β29β31β curPage: Subject<number> = new Subject<number>();
β30β32β nameOrder: Subject<number> = new Subject<number>();
+β β33β optionsSub: Subscription;
β31β34β
β32β35β ngOnInit() {
-β33β β const options: Options = {
-β34β β limit: this.pageSize,
-β35β β skip: (this.curPage - 1) * this.pageSize,
-β36β β sort: { name: this.nameOrder }
-β37β β };
-β38β β
-β39β β this.partiesSub = MeteorObservable.subscribe('parties', options).subscribe(() => {
-β40β β this.parties = Parties.find({}, {
-β41β β sort: {
-β42β β name: this.nameOrder
-β43β β }
-β44β β }).zone();
+β β36β this.optionsSub = Observable.combineLatest(
+β β37β this.pageSize,
+β β38β this.curPage,
+β β39β this.nameOrder
+β β40β ).subscribe(([pageSize, curPage, nameOrder]) => {
+β β41β const options: Options = {
+β β42β limit: pageSize as number,
+β β43β skip: ((curPage as number) - 1) * (pageSize as number),
+β β44β sort: { name: nameOrder as number }
+β β45β };
+β β46β
+β β47β if (this.partiesSub) {
+β β48β this.partiesSub.unsubscribe();
+β β49β }
+β β50β
+β β51β this.partiesSub = MeteorObservable.subscribe('parties', options).subscribe(() => {
+β β52β this.parties = Parties.find({}, {
+β β53β sort: {
+β β54β name: nameOrder
+β β55β }
+β β56β }).zone();
+β β57β });
β45β58β });
β46β59β
β47β60β this.pageSize.next(10);
@@ -59,5 +72,6 @@
β59β72β
β60β73β ngOnDestroy() {
β61β74β this.partiesSub.unsubscribe();
+β β75β this.optionsSub.unsubscribe();
β62β76β }
β63β77β}
Notice that we also removes the Subscription and use
unsubscribe
because we want to drop the old subscription each time it changes.
As this paragraph name suggests, the next logical thing to do would be to implement a pagination UI, which consists of, at least, a list of page links at the bottom of every page, so that the user can switch pages by clicking on these links.
Creating a pagination component is not a trivial task and not one of the primary goals of this tutorial, so we are going to make use of an already existing package with Angular 2 pagination components. Run the following line to add this package:
$ meteor npm install ng2-pagination --save
This package's pagination mark-up follows the structure of the Bootstrap pagination component, so you can change its look simply by using proper CSS styles. It's worth noting, though, that this package has been created with the only this tutorial in mind. It misses a lot of features that would be quite useful in the real world, for example, custom templates.
Ng2-Pagination consists of three main parts:
- pagination controls that render a list of links
- a pagination service to manipulate logic programmatically
- a pagination pipe component, which can be added in any component template, with the main goal to transform a list of items according to the current state of the pagination service and show current page of items on UI
First, let's import the pagination module into our NgModule
:
@@ -3,6 +3,7 @@
β3β3βimport { FormsModule, ReactiveFormsModule } from '@angular/forms';
β4β4βimport { RouterModule } from '@angular/router';
β5β5βimport { AccountsModule } from 'angular2-meteor-accounts-ui';
+β β6βimport { Ng2PaginationModule } from 'ng2-pagination';
β6β7β
β7β8βimport { AppComponent } from './app.component';
β8β9βimport { routes, ROUTES_PROVIDERS } from './app.routes';
@@ -14,7 +15,8 @@
β14β15β FormsModule,
β15β16β ReactiveFormsModule,
β16β17β RouterModule.forRoot(routes),
-β17β β AccountsModule
+β β18β AccountsModule,
+β β19β Ng2PaginationModule
β18β20β ],
β19β21β declarations: [
β20β22β AppComponent,
Because of pagination pipe of ng2-pagination supports only arrays we'll use the PaginationService
.
Let's define the configuration:
@@ -3,6 +3,7 @@
β3β3βimport { Subject } from 'rxjs/Subject';
β4β4βimport { Subscription } from 'rxjs/Subscription';
β5β5βimport { MeteorObservable } from 'meteor-rxjs';
+β β6βimport { PaginationService } from 'ng2-pagination';
β6β7β
β7β8βimport 'rxjs/add/operator/combineLatest';
β8β9β
@@ -32,6 +33,10 @@
β32β33β nameOrder: Subject<number> = new Subject<number>();
β33β34β optionsSub: Subscription;
β34β35β
+β β36β constructor(
+β β37β private paginationService: PaginationService
+β β38β ) {}
+β β39β
β35β40β ngOnInit() {
β36β41β this.optionsSub = Observable.combineLatest(
β37β42β this.pageSize,
@@ -57,6 +62,13 @@
β57β62β });
β58β63β });
β59β64β
+β β65β this.paginationService.register({
+β β66β id: this.paginationService.defaultId,
+β β67β itemsPerPage: 10,
+β β68β currentPage: 1,
+β β69β totalItems: 30,
+β β70β });
+β β71β
β60β72β this.pageSize.next(10);
β61β73β this.curPage.next(1);
β62β74β this.nameOrder.next(1);
id
- this is the identifier of specific pagination, we use the default.
We need to notify the pagination that the current page has been changed, so let's add it to the method where we handle the reactive changes:
@@ -49,6 +49,8 @@
β49β49β sort: { name: nameOrder as number }
β50β50β };
β51β51β
+β β52β this.paginationService.setCurrentPage(this.paginationService.defaultId, curPage as number);
+β β53β
β52β54β if (this.partiesSub) {
β53β55β this.partiesSub.unsubscribe();
β54β56β }
Now, add the pagination controls to the parties-list.component.html
template:
@@ -13,4 +13,6 @@
β13β13β <button (click)="removeParty(party)">X</button>
β14β14β </li>
β15β15β </ul>
+β β16β
+β β17β <pagination-controls></pagination-controls>
β16β18β</div>π«β΅
In the configuration, we provided the current page number, the page size and a new value of total items in the list to paginate.
This total number of items are required to be set in our case, since we don't provide a
regular array of elements but instead an Observable
, the pagination service simply won't know how to calculate its size.
We'll get back to this in the next paragraph where we'll be setting parties total size reactively.
For now, let's just set it to be 30. We'll see why this default value is needed shortly.
The final part is to handle user clicks on the page links. The pagination controls component fires a special event when the user clicks on a page link, causing the current page to update.
Let's handle this event in the template first and then add a method to the PartiesList
component itself:
@@ -14,5 +14,5 @@
β14β14β </li>
β15β15β </ul>
β16β16β
-β17β β <pagination-controls></pagination-controls>
+β β17β <pagination-controls (pageChange)="onPageChanged($event)"></pagination-controls>
β18β18β</div>π«β΅
As you can see, the pagination controls component fires the pageChange
event, calling the onPageChanged
method with
a special event object that contains the new page number to set. Add the onPageChanged
method:
@@ -84,6 +84,10 @@
β84β84β this.parties = Parties.find(value ? { location: value } : {}).zone();
β85β85β }
β86β86β
+β β87β onPageChanged(page: number): void {
+β β88β this.curPage.next(page);
+β β89β }
+β β90β
β87β91β ngOnDestroy() {
β88β92β this.partiesSub.unsubscribe();
β89β93β this.optionsSub.unsubscribe();
At this moment, we have almost everything in place. Let's check if everything is working. We are going to have to add a lot of parties, at least, a couple of pages. But, since we've chosen quite a large default page size (10), it would be tedious to add all parties manually.
In this example, we need to deal with multiple objects and in order to test it and get the best results - we need a lot of Parties objects.
Thankfully, we have a helpful package called anti:fake, which will help us out with the generation of names, locations and other properties of new fake parties.
$ meteor add anti:fake
So, with the following lines of code we are going to have 30 parties in total (given that we already have three):
server/imports/fixtures/parties.ts
:
...
for (var i = 0; i < 27; i++) {
Parties.insert({
name: Fake.sentence(50),
location: Fake.sentence(10),
description: Fake.sentence(100),
public: true
});
}
Fake is loaded in Meteor as a global, you may want to declare it for TypeScript.
You can add it to the end of the typings.d.ts
file:
declare var Fake: {
sentence(words: number): string;
}
Now reset the database (meteor reset
) and run the app. You should see a list of 10 parties shown initially and 3 pages links just at the bottom.
Play around with the pagination: click on page links to go back and forth, then try to delete parties to check if the current page updates properly.
The pagination component needs to know how many pages it will create. As such, we need to know the total number of parties in storage and divide it by the number of items per page.
At the same time, our parties collection will always have no more than necessary parties on the client side. This suggests that we have to add a new publication to publish only the current count of parties existing in storage.
This task looks quite common and, thankfully, it's already been implemented. We can use the tmeasday:publish-counts package.
$ meteor add tmeasday:publish-counts
This package is an example for a package that does not provide it's own TypeScript declaration file, so we will have to manually create and add it to the typings.d.ts
file according to the package API:
@@ -25,4 +25,15 @@
β25β25βdeclare module '*.sass' {
β26β26β const style: string;
β27β27β export default style;
-β28β β}π«β΅
+β β28β}
+β β29β
+β β30βdeclare module 'meteor/tmeasday:publish-counts' {
+β β31β import { Mongo } from 'meteor/mongo';
+β β32β
+β β33β interface CountsObject {
+β β34β get(publicationName: string): number;
+β β35β publish(context: any, publicationName: string, cursor: Mongo.Cursor, options: any): number;
+β β36β }
+β β37β
+β β38β export const Counts: CountsObject;
+β β39β}
This package exports a Counts
object with all of the API methods we will need.
Notice that you'll see a TypeScript warning in the terminal saying that "Counts" has no method you want to use, when you start using the API. You can remove this warning by adding a publish-counts type declaration file to your typings.
Let's publish the total number of parties as follows:
@@ -1,4 +1,6 @@
β1β1βimport { Meteor } from 'meteor/meteor';
+β β2βimport { Counts } from 'meteor/tmeasday:publish-counts';
+β β3β
β2β4βimport { Parties } from '../../../both/collections/parties.collection';
β3β5β
β4β6βinterface Options {
@@ -6,6 +8,8 @@
β 6β 8β}
β 7β 9β
β 8β10βMeteor.publish('parties', function(options: Options) {
+β β11β Counts.publish(this, 'numberOfParties', Parties.collection.find(buildQuery.call(this)), { noReady: true });
+β β12β
β 9β13β return Parties.find(buildQuery.call(this), options);
β10β14β});
Notice that we are passing
{ noReady: true }
in the last argument so that the publication will be ready only after our main cursor is loaded, instead of waiting for Counts.
We've just created the new numberOfParties publication.
Let's get it reactively on the client side using the Counts
object, and, at the same time,
introduce a new partiesSize
property in the PartiesList
component:
@@ -4,6 +4,7 @@
β 4β 4βimport { Subscription } from 'rxjs/Subscription';
β 5β 5βimport { MeteorObservable } from 'meteor-rxjs';
β 6β 6βimport { PaginationService } from 'ng2-pagination';
+β β 7βimport { Counts } from 'meteor/tmeasday:publish-counts';
β 7β 8β
β 8β 9βimport 'rxjs/add/operator/combineLatest';
β 9β10β
@@ -32,6 +33,8 @@
β32β33β curPage: Subject<number> = new Subject<number>();
β33β34β nameOrder: Subject<number> = new Subject<number>();
β34β35β optionsSub: Subscription;
+β β36β partiesSize: number = 0;
+β β37β autorunSub: Subscription;
β35β38β
β36β39β constructor(
β37β40β private paginationService: PaginationService
@@ -68,12 +71,17 @@
β68β71β id: this.paginationService.defaultId,
β69β72β itemsPerPage: 10,
β70β73β currentPage: 1,
-β71β β totalItems: 30,
+β β74β totalItems: this.partiesSize
β72β75β });
β73β76β
β74β77β this.pageSize.next(10);
β75β78β this.curPage.next(1);
β76β79β this.nameOrder.next(1);
+β β80β
+β β81β this.autorunSub = MeteorObservable.autorun().subscribe(() => {
+β β82β this.partiesSize = Counts.get('numberOfParties');
+β β83β this.paginationService.setTotalItems(this.paginationService.defaultId, this.partiesSize);
+β β84β });
β77β85β }
β78β86β
β79β87β removeParty(party: Party): void {
@@ -91,5 +99,6 @@
β 91β 99β ngOnDestroy() {
β 92β100β this.partiesSub.unsubscribe();
β 93β101β this.optionsSub.unsubscribe();
+β β102β this.autorunSub.unsubscribe();
β 94β103β }
β 95β104β}
We used MeteorObservable.autorun
because we wan't to know when there are changes regarding the data that comes from Meteor - so now every change of data, we will calculate the total number of parties and save it in our Component, then we will set it in the PaginationService
.
Let's verify that the app works the same as before. Run the app. There should be same three pages of parties.
What's more interesting is to add a couple of new parties, thus, adding a new 4th page. By this way, we can prove that our new "total number" publication and pagination controls are all working properly.
It's time for a new cool feature Socially users will certainly enjoy - sorting the parties list by party name. At this moment, we know everything we need to implement it.
As previously implements, nameOrder
uses one of two values, 1 or -1, to express ascending and descending orders
respectively. Then, as you can see, we assign nameOrder
to the party property (currently, name
) we want to sort.
We'll add a new dropdown UI control with two orders to change, ascending and descending. Let's add it in front of our parties list:
@@ -5,6 +5,15 @@
β 5β 5β
β 6β 6β <login-buttons></login-buttons>
β 7β 7β
+β β 8β <h1>Parties:</h1>
+β β 9β
+β β10β <div>
+β β11β <select #sort (change)="changeSortOrder(sort.value)">
+β β12β <option value="1" selected>Ascending</option>
+β β13β <option value="-1">Descending</option>
+β β14β </select>
+β β15β </div>
+β β16β
β 8β17β <ul>
β 9β18β <li *ngFor="let party of parties | async">
β10β19β <a [routerLink]="['/party', party._id]">{{party.name}}</a>
In the PartiesList
component, we change the nameOrder
property to be a reactive variable and add a changeSortOrder
event handler, where we set the new sort order:
@@ -96,6 +96,10 @@
β 96β 96β this.curPage.next(page);
β 97β 97β }
β 98β 98β
+β β 99β changeSortOrder(nameOrder: string): void {
+β β100β this.nameOrder.next(parseInt(nameOrder));
+β β101β }
+β β102β
β 99β103β ngOnDestroy() {
β100β104β this.partiesSub.unsubscribe();
β101β105β this.optionsSub.unsubscribe();
Calling
next
onnameOrder
Subject, will trigger the change notification - and then the Meteor subscription will re-created with the new parameters!
That's just it! Run the app and change the sort order back and forth.
What's important here is that pagination updates properly, i.e. according to a new sort order.
Before this step we had a nice feature to search parties by location, but with the addition of pagination, location search has partly broken. In its current state, there will always be no more than the current page of parties shown simultaneously on the client side. We would like, of course, to search parties across all storage, not just across the current page.
To fix that, we'll need to patch our "parties" and "total number" publications on the server side to query parties with a new "location" parameter passed down from the client. Having that fixed, it should work properly in accordance with the added pagination.
So, let's add filtering parties by the location with the help of Mongo's regex API. It is going to look like this:
@@ -7,10 +7,12 @@
β 7β 7β [key: string]: any;
β 8β 8β}
β 9β 9β
-β10β βMeteor.publish('parties', function(options: Options) {
-β11β β Counts.publish(this, 'numberOfParties', Parties.collection.find(buildQuery.call(this)), { noReady: true });
+β β10βMeteor.publish('parties', function(options: Options, location?: string) {
+β β11β const selector = buildQuery.call(this, null, location);
β12β12β
-β13β β return Parties.find(buildQuery.call(this), options);
+β β13β Counts.publish(this, 'numberOfParties', Parties.collection.find(selector), { noReady: true });
+β β14β
+β β15β return Parties.find(selector, options);
β14β16β});
β15β17β
β16β18βMeteor.publish('party', function(partyId: string) {
@@ -18,7 +20,7 @@
β18β20β});
β19β21β
β20β22β
-β21β βfunction buildQuery(partyId?: string): Object {
+β β23βfunction buildQuery(partyId?: string, location?: string): Object {
β22β24β const isAvailable = {
β23β25β $or: [{
β24β26β // party is public
@@ -48,5 +50,13 @@
β48β50β };
β49β51β }
β50β52β
-β51β β return isAvailable;
+β β53β const searchRegEx = { '$regex': '.*' + (location || '') + '.*', '$options': 'i' };
+β β54β
+β β55β return {
+β β56β $and: [{
+β β57β location: searchRegEx
+β β58β },
+β β59β isAvailable
+β β60β ]
+β β61β };
β52β62β}π«β΅
On the client side, we are going to add a new reactive variable and set it to update when a user clicks on the search button:
@@ -35,6 +35,7 @@
β35β35β optionsSub: Subscription;
β36β36β partiesSize: number = 0;
β37β37β autorunSub: Subscription;
+β β38β location: Subject<string> = new Subject<string>();
β38β39β
β39β40β constructor(
β40β41β private paginationService: PaginationService
@@ -44,8 +45,9 @@
β44β45β this.optionsSub = Observable.combineLatest(
β45β46β this.pageSize,
β46β47β this.curPage,
-β47β β this.nameOrder
-β48β β ).subscribe(([pageSize, curPage, nameOrder]) => {
+β β48β this.nameOrder,
+β β49β this.location
+β β50β ).subscribe(([pageSize, curPage, nameOrder, location]) => {
β49β51β const options: Options = {
β50β52β limit: pageSize as number,
β51β53β skip: ((curPage as number) - 1) * (pageSize as number),
@@ -58,7 +60,7 @@
β58β60β this.partiesSub.unsubscribe();
β59β61β }
β60β62β
-β61β β this.partiesSub = MeteorObservable.subscribe('parties', options).subscribe(() => {
+β β63β this.partiesSub = MeteorObservable.subscribe('parties', options, location).subscribe(() => {
β62β64β this.parties = Parties.find({}, {
β63β65β sort: {
β64β66β name: nameOrder
@@ -77,6 +79,7 @@
β77β79β this.pageSize.next(10);
β78β80β this.curPage.next(1);
β79β81β this.nameOrder.next(1);
+β β82β this.location.next('');
β80β83β
β81β84β this.autorunSub = MeteorObservable.autorun().subscribe(() => {
β82β85β this.partiesSize = Counts.get('numberOfParties');
@@ -89,7 +92,8 @@
β89β92β }
β90β93β
β91β94β search(value: string): void {
-β92β β this.parties = Parties.find(value ? { location: value } : {}).zone();
+β β95β this.curPage.next(1);
+β β96β this.location.next(value);
β93β97β }
β94β98β
β95β99β onPageChanged(page: number): void {
Notice that we don't know what size to expect from the search that's why we are re-setting the current page to 1.
Let's check it out now that everything works properly altogether: pagination, search, sorting, removing and addition of new parties.
For example, you can try to add 30 parties in a way mentioned slightly above; then try to remove all 30 parties; then sort by the descending order; then try to search by Palo Alto β it should find only two, in case if you have not added any other parties rather than used in this tutorial so far; then try to remove one of the found parties and, finally, search with an empty location.
Although this sequence of actions looks quite complicated, it was accomplished with rather few lines of code.
This step covered a lot. We looked at:
- Mongo query sort options:
sort
,limit
,skip
- RxJS
Subject
for updating variables automatically - Handling onChange events in Angular 2
- Generating fake data with
anti:fake
- Establishing the total number of results with
tmeasday:publish-counts
- Enabling server-side searching across an entire collection
In the next step we'll look at sending out our party invitations and look deeper into pipes.
}: # {: (footer) {: (nav_step)
< Previous Step | Next Step > |
---|---|
}: # | |
}: # |