-
Notifications
You must be signed in to change notification settings - Fork 12
/
12-pagination.md.erb
467 lines (340 loc) · 22 KB
/
12-pagination.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
---
title: Pagination
slug: pagination
date: 0012/01/01
number: 12
contents: Lerne mehr über Meteor's Subscriptions und wie wir diese benützen können um Daten zu kontrollieren.|Implementiere eine unendliche Paginierung.|Benutze das iron-router-progress Package um eine schöne iOS-style Progress-Bar einzurichten.|Erstelle eine spezielle Subscription um direct-links zu Posts zu unterstützen.
paragraphs: 67
---
Alles sieht gut aus bei Microscope und wir können damit rechnen, dass es sich zu einem grossen Hit entwickelt, sobald es released wird.
Deswegen müssen wir etwas vorausdenken, und uns um Performance Auswirkungen, der Anzahl der neuen Posts, die eingetragen werden kümmern.
Wir haben bisher angeschaut, wie eine Client Collection nur ein Teilsatz der Daten vom Server enthalten soll. Dies haben wir für die Benachrichtigungs- und Comments Collection schon implementiert.
Jedoch: Zur Zeit publizieren wir immer noch alle Posts an alle verbundenen Benutzer. Sollten eines Tages tausende von Links geposted werden, wird das problematisch. Um dieses Problem anzugehen bauen wir eine Paginierung der Posts ein.
### Mehr Posts hinzufügen
Zuerst müssen wir unsere Fixtures anpassen und genug Posts laden damit Paginierung auch Sinn macht.
~~~js
// Fixture data
if (Posts.find().count() === 0) {
//...
Posts.insert({
title: 'The Meteor Book',
userId: tom._id,
author: tom.profile.name,
url: 'http://themeteorbook.com',
submitted: now - 12 * 3600 * 1000,
commentsCount: 0
});
for (var i = 0; i < 10; i++) {
Posts.insert({
title: 'Test post #' + i,
author: sacha.profile.name,
userId: sacha._id,
url: 'http://google.com/?q=test-' + i,
submitted: now - i * 3600 * 1000,
commentsCount: 0
});
}
}
~~~
<%= caption "server/fixtures.js" %>
<%= highlight "15~24" %>
Nachdem wir `meteor reset` ausgeführt haben, müssen wir so etwas schreiben:
<%= screenshot "12-1", "Displaying dummy data. " %>
<%= commit "12-1", "Added enough posts that pagination is necessary." %>
### Unendliche Paginierung
Wir werden eine unendliche Paginierung implementieren. Das heisst, dass wir zuerst einmal nur 10 Posts auf den Screen rendern und einen Link "load more" zur Verfügung stellen. Sobald wir diesen Link klicken, sollen 10 weitere Posts der Liste hinzugefügt werden. Dieser Prozess soll unendlich wiederholbar sein. Das heisst, dass wir unsere gesamte Paginierung mit einem einzigen Parameter (der Anzahl Posts) steuern können.
Wir müssen dem Server nun Informationen über diesen Parameter liefern, damit er weiss, wieviele Posts er an den Client ausliefern soll. Da wir im Router schon eine Subscription zu den `posts` haben, ist das auch der Ort wo wir diese Informationen weiterleiten.
Der einfachste Weg dies zu vollziehen, ist die Einschränkung der Anzahl der Posts über einen Pfad-Parameter an den Server zu übergeben. Dies ergibt uns solche URLs: `http://localhost:3000/25`. Ein Vorteil die URL Steuerung anderen Methoden vorzuziehen, ist die Tatsache dass wir den Zustand der Applikation in gewisser Art und Weise in der URL persistiert haben, sollte ein Benutzer die Seite aus Versehen neu laden, werden nach wie vor 25 Posts angezeigt.
Um dies sauber aufzuziehen müssen wir unsere Subscription etwas abändern. Genau so wie im Comments Kapitel, müssen wir unseren Subscription Code von der Router Ebene auf die Route Ebene herunterzügeln.
Das mag ein bisschen viel aufs Mal zu sein, aber es wird bestimmt klarer mit dem Code.
Zuerst müssen wir die Subscription zu den Posts in `Router.configure()` stoppen. Lösche `Meteor.subscribe('posts')`. Es bleibt nur die `notifications` subscription:
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() {
return [Meteor.subscribe('notifications')]
}
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "5" %>
Dann müssen wir einen `postsLimit` Paramater dem Pfad anhängen. Das ? nach dem Parameter bedeutet dass der Parameter optional ist. Somit matcht unsere Route nicht nur `http://localhost:3000/50`, sondern auch `http://localhost:3000`.
~~~js
Router.map(function() {
//...
this.route('postsList', {
path: '/:postsLimit?'
});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "5" %>
An dieser Stelle wichtig zu erwähnen dass ein Pfad mit der Formel `/:parameter?` jeden möglichen Pfad matcht. Da jede Route nacheinander geparsed wird, um zu sehen ob ein Match mit dem jetzigen Pfad vorliegt, müssen wir sicherstellen, dass wir unsere Routes so arrangieren dass ihre Spezifizität abnimmt.
Anders ausgedrückt, Routes die spezifischere Muster wie `/posts/:_id` abfangen sollen, sollten im Code zuerst kommen. Unsere `postsList` Route sollte ans Ende des Files verschoben werden, da diese so ziemlich alles matcht.
Es ist nun Zeit die Knacknuss des Subscriben und des Auffindens der richtigen Daten zu lösen. Da wir auch den Fall abfangen wollen, wo kein `postsLimit` Paramater vorhanden ist, müssen wir einen default Wert definieren. Wir tragen dafür mal "5" ein, um mit der Applikation herumzuspielen.
~~~js
Router.map(function() {
//..
this.route('postsList', {
path: '/:postsLimit?',
waitOn: function() {
var postsLimit = parseInt(this.params.postsLimit) || 5;
return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: postsLimit});
}
});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "6~9" %>
Nun geben wir mit der Posts Publication auch noch ein JavaScript Objekt (`{limit: postsLImit}`) mit. Dieses Objekt dient als Options-Objekt für die serverseitige `Posts.find()` Methode. Wir implementieren dort folgenden Code:
~~~js
Meteor.publish('posts', function(options) {
return Posts.find({}, options);
});
Meteor.publish('comments', function(postId) {
return Comments.find({postId: postId});
});
Meteor.publish('notifications', function() {
return Notifications.find({userId: this.userId});
});
~~~
<%= caption "server/publications.js" %>
<%= highlight "1~3" %>
<% note do %>
### Parameter übergeben
Unser Publications Code lässt den Server wissen, dass er allen JavaScript Objekten trauen kann die der Client ihm zusendet (hier, `{limit: postsLimit}`) und für die `options` des `find()` Statements gebrauchen kann. Dies macht es für Benutzer möglich jegliche Optionen via Browser Console abzusenden.
In unserem Beispiel ist dies ziemlich harmlos, da alles was ein Benutzer tun könnte, eine andere Anordnung der Posts erzwingen ist. (Oder er könnte die Limite anders setzen, was ja eigentliche unser initiales Ziel war).
Dieses Muster sollte allerdings vermieden werden, sobald private Daten in nicht publizierten Feldern gespeichert werden. Solche Felder könnten vom Benutzer manipuliert werden. Aus denselben Gründen sollte auch für das Selector Argument des `find()` Statements sollte diese Anwendung vermieden werden.
Ein sichereres Pattern könnte es sein die Parameter separat zu übergeben (anstatt in Form eines Objekts), um sicher zu sein dass wir die volle Kontrolle über die Daten behalten:
~~~js
Meteor.publish('posts', function(sort, limit) {
return Posts.find({}, {sort: sort, limit: limit});
});
~~~
<% end %>
Da wir jetzt nun auf dem Route-Level subscriben, würde es Sinn machen den `data context` auch hier festzulegen. wir weiche ein wenig vom vorherigen Pattern ab und erstellen eine data Funktion die anstelle eines Cursor ein JavaScript Objekt zurückliefert. So können wir dem `data context` den Namen `posts` geben.
Das heisst, dass anstelle der impliziten Verfügbarkeit von `this` im Template, unser `data context` wird als posts verfügbar sein. Abgesehen von diesem kleinen Unterschied, sollte dir der Code bekannt vorkommen:
~~~js
Router.map(function() {
this.route('postsList', {
path: '/:postsLimit?',
waitOn: function() {
var limit = parseInt(this.params.postsLimit) || 5;
return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: limit});
},
data: function() {
var limit = parseInt(this.params.postsLimit) || 5;
return {
posts: Posts.find({}, {sort: {submitted: -1}, limit: limit})
};
}
});
//..
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "8~13" %>
Da wir den `data context` im Route Level zur Verfügung haben, können wir den Template helper vom `posts` Template getrost löschen. Und da unser `data context` auch noch den selben Namen (nämlich posts) hat, brauchen wir nicht einmal unser `postsList` Template anzupassen.
Wir rekapitulieren. So sieht unsere neue, verbesserte `router.js` aus:
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() {
return [Meteor.subscribe('notifications')]
}
});
Router.map(function() {
//...
this.route('postsList', {
path: '/:postsLimit?',
waitOn: function() {
var limit = parseInt(this.params.postsLimit) || 5;
return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: limit});
},
data: function() {
var limit = parseInt(this.params.postsLimit) || 5;
return {
posts: Posts.find({}, {sort: {submitted: -1}, limit: limit})
};
}
});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "5, 11~21" %>
<%= commit "12-2", "Augmented the postsList route to take a limit." %>
Versuchen wir unser brandneues Paginierungssystem aus. Wir könnnen nun eine beliebige Anzahl von Posts auf unserer Homepage laden, indem wir einfach am URL Parameter rumschrauben. Zum Beispiel: `http://localhost:3000/3`, sollte in etwa so aussehen:
<%= screenshot "12-2", "Controlling the number of posts on the homepage. " %>
<% note do %>
### Wieso keine Pages?
Wieso implementieren wir eine "unendliche Paginierung" und nicht eine fortlaufende Paginierung mit jeweils 10 Posts, so wie die Google Suchabfrage? Die Antwort lautet: wegen dem real-time Paradigma das Meteor verfolgt.
Stell dir vor wir paginieren unsere `Posts` Collection mit dem Google Suchresultate-Paginierungspattern und dass wir uns gerade auf der zweiten Seite befinden. Diese zeigt Suchresultate 10 bis 20. Was passiert nun wenn ein anderer Benutzer gleichzeitig die 10 vorherigen Posts löscht?
Da unsere App real-time ist, würde unser Datenstamm sich dynamisch verändern. Post 10 wäre jetzt post 9 und aus unserer View hinausfliegen, während post 11 in unserem Range wäre. Das Resultat wäre, dass der Benutzer plötzlich die Posts ändern sehen würde, ohne nachvollziehbaren Grund.
Selbst wenn wir diese UX-Eigenart akzeptieren würden, wäre eine Implementierung von traditionellem Paging aus technischer Sicht schwierig.
Zurück zu unserem vorherigen Beispiel. Wir publizieren `Posts` 10 bis 20 von unserer `Posts` Collection, aber wie soll der Client diese Posts ausfindig machen? Es ist nicht möglich Posts 10 bis 20 herauszupicken da im Client-seitigen Datensatz total nur 10 Posts vorhanden sind.
Eine Lösung hierzu wäre einfach die 10 Posts auf dem Server zu publizieren und dann einen `Posts.find()` auf dem Client ausführen um alle publizierten Posts zu laden.
Dies funktioniert aber nur wenn es eine Einzel-Subscription ist. Aber was wenn es mehr als eine `Post` Subscription gibt wie wir bald sehen werden?
Nehmen wir einmal an eine Subscription fragt die Posts 10 bis 20 an, eine andere die Posts 30 bis 40. Also werden insgesamt 20 Posts client-seitig geladen, ohne dass wir wissen welche Posts zu welcher Subscription gehören.
Für all diese Gründe macht die traditionelle Paginierung mit Meteor keinen Sinn.
<% end %>
### Einen Route Controller erstellen
Vielleicht hast du festgestellt dass wir die Zeile `var limit = parseInt(this.params.postsLimit) || 5;` zweimal wiederholen. Auch das hard-coding der Anzahl "5" ist nicht gerade ideal. Das ist nicht das Ende der Welt, aber da es besser ist dem DRY (Don't repeat Yourself) Prinzip zu folgen, sollten wir unseren Code refactorn.
Wir stellen einen neuen Aspekt von Iron Router vor: Route Controllers. Ein Route Controller ist einfach eine Art, Routing-Features in einer wiederverwendbaren Art zu verpacken und wovon jede Route erben kann. Hier brauchen wir es nur für eine Single Route, aber schon im nächsten Kapitel gebrauchen wir das Feature auf äusserst praktische Art und Weise.
~~~js
PostsListController = RouteController.extend({
template: 'postsList',
increment: 5,
limit: function() {
return parseInt(this.params.postsLimit) || this.increment;
},
findOptions: function() {
return {sort: {submitted: -1}, limit: this.limit()};
},
waitOn: function() {
return Meteor.subscribe('posts', this.findOptions());
},
data: function() {
return {posts: Posts.find({}, this.findOptions())};
}
});
Router.map(function() {
//...
this.route('postsList', {
path: '/:postsLimit?',
controller: PostsListController
});
});
~~~
<%= caption "lib/router.js" %>
Gehen wir die Schritte durch. Indem wir den `RouteController` erweitern erstellen wir unseren Controller. Dann setzen wir die `template` property genau wie vorher und fügen noch die `increment` property dazu.
Weiter definieren wir eine `limit` Funktion, welche die aktuelle Limite zurückliefert und eine `findOptions` Funktion, die ein options Objekt zurückliefert. Dies scheint wie eine Zusatzstufe, aber wir brauchen sie später noch.
Danach definieren wir noch unsere `waitOn` und data Funktionen genauso wie vorher, ausser dass diese nun auf unsere neue `findOptions` Funktion zugreifen.
Zuletzt müssen wir noch der `postsList` route mitteilen, dass sie unser neuen Controller benutzen soll. Dies machen wir über die `controller` property.
<%= commit "12-3", "Refactored postsLists route into a RouteController." %>
### Einen Load More Link hinzufügen
Jetzt haben wir eine funktionierende Paginierung und unser Code schaut soweit gut aus. Das einzige Problem: Die einzige Möglichkeit die Paginierung zu benutzen ist über die URL. Dies ist definitiv keine tolle User Experience. Das kriegen wir besser hin.
Was wir tun ist ziemlich einfach. Wir fügen einen "load more" Button am Ende der Post Liste hinzu. Dieser inkrementiert die Anzahl der aktuell angezeigten Posts bei jedem Klick um fünf. Wenn ich zur Zeit also auf der URL URL `http://localhost:3000/5` bin und auf "load more" klicke, soll die URL auf URL `http://localhost:3000/10` ändern. Wenn du es in diesem Buch bis hierher gebracht hast, sind wir zuversichtlich, dass du auch diese kleine arithmetische Aufgabe meistern wirst.
Wie vorher fügen wir unsere Paginierungs-Logik in unsere Route. Erinnere dich das wir unseren data context explizit benannt haben, anstatt einen anonymen Cursor zu verwenden. Da es keine Regel gibt die vorschreibt, dass mein der data Funktion nur Cursor übergeben kann, benutzen wir dieselbe Technik um die URL für den "load more" Button zu erstellen.
~~~js
PostsListController = RouteController.extend({
template: 'postsList',
increment: 5,
limit: function() {
return parseInt(this.params.postsLimit) || this.increment;
},
findOptions: function() {
return {sort: {submitted: -1}, limit: this.limit()};
},
waitOn: function() {
return Meteor.subscribe('posts', this.findOptions());
},
posts: function() {
return Posts.find({}, this.findOptions());
},
data: function() {
var hasMore = this.posts().fetch().length === this.limit();
var nextPath = this.route.path({postsLimit: this.limit() + this.increment});
return {
posts: this.posts(),
nextPath: hasMore ? nextPath : null
};
}
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "16~23" %>
Nun schauen wir etwas tiefer in diese Router Magie. Die postsList Route, die vom `PostsListController` den wir aktuell verwenden erbt, nimmt einen `postsLimit` Parameter an.
Wenn wir also `this.route.path()` mit `{postsLimit: this.limit() + this.increment}` füttern, sagen wir der postsList Route, sie soll ihren eigenen Pfad mit dem übergebenen Javascript Objekt zusammenbauen.
Dies ist genau dasselbe Prinzip wie die Benutzung vom `{{pathFor 'postsList'}}`-Spacebars helper, ausser dass wir das implizite this mit unserem selbstgemachten `data context` ersetzten.
Wir nehmen den Pfad und fügen ihn zum data context unseres Templates hinzu, aber nur wenn es noch mehr Posts zum anzeigen gibt. Wie war das machen ist etwas knifflig.
Wir wissen, dass `this.limit()` die aktuelle Anzahl die wir anzeigen möchten zurückgibt. Dies ist entweder der Wert der aus der aktuellen URL kommt, oder unser Default Wert (5) falls die URL keine Parameter hat.
Dann haben wir noch `this.posts`, welches zum aktuellen Cursor zeigt. Also referenziert `this.posts.count()` auf die Anzahl von Posts, die aktuell im Cursor enthalten sind.
Was wir hiermit sagen wollen: Wenn wir eine Anfrage für n Posts machen und n Posts zurückerhalten, dann zeigen wir den "load more" Link an. Wenn wir aber eine Anfrage für n Posts machen und weniger als n Posts zurückerhalten, heisst das, dass wir die Limite erreicht haben und den Button nicht mehr anzuzeigen brauchen.
In einem Fall versagt aber unser System noch: Wenn die Anzahl Posts in der Datenbank genau n ist. In diesem Fall fragt der Client n Posts an und erhält n Posts zurück. Der "load more" Button wird aber immer noch gerendert, nicht wissend dass es nicht mehr `Posts` gibt.
Dummerweise gibt es keine einfachen Workarounds für dieses Problem, deswegen müssen wir uns für den Moment mit einer nicht-ganz-perfekten Lösung zufrieden geben.
Was wir noch machen müssen, ist sicherstellen dass der "load more" Link am Ende der Postsliste anzuzeigen, aber nur wenn wir keine `Posts` mehr zu laden haben.
~~~html
<template name="postsList">
<div class="posts">
{{#each posts}}
{{> postItem}}
{{/each}}
{{#if nextPath}}
<a class="load-more" href="{{nextPath}}">Load more</a>
{{/if}}
</div>
</template>
~~~
<%= caption "client/views/posts/posts_list.html" %>
<%= highlight "7~10" %>
So sollte die Post-Liste aussehen:
<%= screenshot "12-3", "The “load more” button. " %>
<%= commit "12-4", "Added nextPath() to the controller and use it to step through posts." %>
### Einen besseren Fortschrittsbalken
Unsere Paginierung funktioniert nun anständig, hat aber noch eine Macke: Jedesmal wenn wir "load more" klicken und der Router mehr Posts von der DB anfordert, werden wir zum `loading` Template gesendet, während wir auf die neuen Daten warten. Wir werden also jedesmal zum Seitenanfang geschickt und müssen zurückscrollen wo wir eigentlich waren.
Es wäre natürlich viel besser, wenn wir während dem ganzen Vorgang an Ort und Stelle bleiben würden, aber trotzdem eine Art von Feedback erhalten, dass zur Zeit gerade Daten geladen werden. Zum Glück gibt es das `iron-router-progress` Package, dass genau für diesen Fall gemacht wurde.
Ähnlich wie in iOS's Safari oder auf Sites wie Medium oder YouTube, `iron-router-progress` fügt eine kleine Loading-Bar an den Bildschirmanfang. Die Implementation ist ähnlich einfach wie das Hinzufügen des Packages zur Applikation.
~~~bash
mrt add iron-router-progress
~~~
<%= caption "bash console" %>
Durch die Magie von smart packages, funktioniert unser Fortschritts-Balken wie von Geisterhand. Sie wird für alle Routes aktiviert und hört automatisch auf zu laden, sobald der Ladeprozess fertig ist.
Wir machen noch eine Verbesserung: Wir schalten das `iron-router-progress` Package für die `postSubmint` Route ab, da hier nicht auf Subscription-Daten gewartet wird (es ist ja bloss ein Formular):
~~~js
Router.map(function() {
//...
this.route('postSubmit', {
path: '/submit',
disableProgress: true
});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "7" %>
<%= commit "12-5", "Use the iron-router-progress package to make pagination nicer." %>
### Auf irgendeinen Post zugreifen
Zur Zeit laden wir die fünf neusten Posts standardmässig, was passiert aber wenn jemand auf irgendeine Post-Page zugreifen möchte?
<%= screenshot "12-4", "An empty template." %>
Wenn das zur Zeit versucht wird, wird das leere `Post`-Template angezeigt. Das macht Sinn, denn wir haben dem Router mitgeteilt er soll zur Post-List subscriben, sobald die `postList` Route aufgerufen wird, aber wir machen keine Angaben, was zu tun ist, wenn die postPage geroutet wird.
Bisher wissen wir nur wie man eine Liste der letzten n Posts subscribed. Wie fordern wir vom Server einen einzelnen Post an? Es ist Zeit ein kleines Geheimnis zu lüften: Du kannst mehr als eine Publication für jede Collection haben.
Damit wir die fehlenden Posts wieder abrufen können, erstellen wir ganz einfache eine separate `singlePost` Publication die nur einen Post publiziert, der über die `_id` identifiziert wird.
~~~js
Meteor.publish('posts', function(options) {
return Posts.find({}, options);
});
Meteor.publish('singlePost', function(id) {
return id && Posts.find(id);
});
~~~
<%= caption "server/publications.js" %>
<%= highlight "5~7" %>
Nun müssen wir im Client noch für die richtigen Posts subscriben. Wir haben schon eine Subscription auf die `Comments` Publication auf der `postPage` Route laufen, deswegen können wir die `singlePost`-Subscription hier hinzufügen. Auch dürfen wir nicht vergessen unsere Subscription ebenfalls der `postEdit` Route zur Verfügung zu stellen, da diese auch auf diese Daten zugreifen will.
~~~js
Router.map(function() {
//...
this.route('postPage', {
path: '/posts/:_id',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('comments', this.params._id)
];
},
data: function() { return Posts.findOne(this.params._id); }
});
this.route('postEdit', {
path: '/posts/:_id/edit',
waitOn: function() {
return Meteor.subscribe('singlePost', this.params._id);
},
data: function() { return Posts.findOne(this.params._id); }
});
/...
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "7~12,18~20" %>
<%= commit "12-6","Use a single post subscription to ensure that we can always see the right post." %>
Mit der Implementierung der Paginierung haben wir die Skalierbarkeits-Probleme der App behoben. Unsere Benutzer werden uns das mit dem Posten von mehr Links denn je danken. Wäre es nicht toll ein Bewertungssystem für die Posts zu haben? Genau um das geht es im nächsten Kapitel.