Skip to content

Commit

Permalink
Fully load item children when viewing item page
Browse files Browse the repository at this point in the history
Should prevent the 'trickle effect' of comments loading after the page
has been opened, as well will load all of the comments before the
activate function on the route resolves.
  • Loading branch information
michaelbull committed Mar 27, 2018
1 parent 8490e0d commit e430323
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 54 deletions.
2 changes: 1 addition & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from 'aurelia-router';
import * as NProgress from 'nprogress';
import '../style/index.scss';
import { ScrollToTopStep } from './services/scroll-to-top-step';
import { ScrollToTopStep } from './pipelines/scroll-to-top-step';

const MS_FOR_LOADER_BAR_TO_APPEAR = 50;

Expand Down
16 changes: 8 additions & 8 deletions src/components/comment.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<template class="comment ${expanded ? '' : 'comment--collapsed'}">
<div class="comment__author">
<a class="comment__author-link" route-href="route: user; params.bind: { id: comment.by }">${comment.by}</a>
<a class="comment__author-link" route-href="route: user; params.bind: { id: item.value.by }">${item.value.by}</a>
&ensp;
<a class="comment__link" route-href="route: item; params.bind: { id: comment.id}">${comment.time | timeago}</a>
<a class="comment__link" route-href="route: item; params.bind: { id: item.value.id}">${item.value.time | timeago}</a>
</div>

<button class="comment__toggle ${expanded ? '' : 'comment__toggle--collapsed'}" click.delegate="toggle()">
Expand All @@ -12,19 +12,19 @@
<hn-text
class="comment__text"
if.bind="expanded"
value.bind="comment.text">
value.bind="item.value.text">
</hn-text>

<template if.bind="depth === MAX_DEPTH">
<a class="comment__more-link" route-href="route: item; params.bind: { id: comment.id}">View more replies…</a>
<a class="comment__more-link" route-href="route: item; params.bind: { id: item.value.id}">View more replies…</a>
</template>

<ul class="comment__reply" if.bind="replies.length > 0 && depth < MAX_DEPTH && expanded">
<template repeat.for="reply of replies">
<ul class="comment__reply" if.bind="item.children.length > 0 && depth < MAX_DEPTH && expanded">
<template repeat.for="child of item.children">
<li
if.bind="reply !== null && !reply.deleted"
if.bind="child !== null && !child.value.deleted"
as-element="hn-comment"
comment.bind="reply"
item.bind="child"
depth.bind="depth + 1">
</li>
</template>
Expand Down
12 changes: 2 additions & 10 deletions src/components/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import {
customElement
} from 'aurelia-framework';
import { Item } from '../models/item';
import { Trie } from '../models/trie';
import { HackerNewsApi } from '../services/api';

@autoinject()
@customElement('hn-comment')
export class Comment {
readonly MAX_DEPTH = 6;

replies: Item[] = [];
expanded = true;

@bindable() comment!: Item;
@bindable() item!: Trie<Item>;
@bindable() depth!: number;

private readonly api: HackerNewsApi;
Expand All @@ -23,14 +23,6 @@ export class Comment {
this.api = api;
}

async bind(): Promise<void> {
if (this.comment.kids === undefined || this.comment.kids.length < 1) {
return;
}

this.replies = await this.api.fetchItems(this.comment.kids);
}

toggle(): void {
this.expanded = !this.expanded;
}
Expand Down
4 changes: 4 additions & 0 deletions src/models/trie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Trie<T> {
value: T;
children: (Trie<T> | null)[];
}
22 changes: 11 additions & 11 deletions src/pages/item.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,33 @@
</template>

<template else>
<template if.bind="item.deleted">
<template if.bind="item.value.deleted">
Item ${item.id} has been deleted.
</template>

<template else>
<template if.bind="item.parent !== undefined">
<a class="news-item__parent" route-href="route: item; params.bind: { id: item.parent }">View Parent &raquo;</a>
<template if.bind="item.value.parent !== undefined">
<a class="news-item__parent" route-href="route: item; params.bind: { id: item.value.parent }">View Parent &raquo;</a>
</template>

<hn-item-preview item.bind="item"></hn-item-preview>
<hn-item-preview item.bind="item.value"></hn-item-preview>

<hn-text
class="news-item__text"
if.bind="item.text"
value.bind="item.text">
if.bind="item.value.text"
value.bind="item.value.text">
</hn-text>

<p class="news-item__no-comments" if.bind="item.type === 'story' && comments.length < 1">
<p class="news-item__no-comments" if.bind="item.value.type === 'story' && comments.length < 1">
No comments yet.
</p>

<ul class="news-item__comments" if.bind="comments.length > 0">
<template repeat.for="comment of comments">
<ul class="news-item__comments" if.bind="item.children.length > 0">
<template repeat.for="child of item.children">
<li
if.bind="comment !== null && !comment.deleted"
if.bind="child !== null && !child.value.deleted"
as-element="hn-comment"
comment.bind="comment"
item.bind="child"
depth.bind="1">
</li>
</template>
Expand Down
16 changes: 5 additions & 11 deletions src/pages/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
RouteConfig
} from 'aurelia-router';
import { Item } from '../models/item';
import { Trie } from '../models/trie';
import { HackerNewsApi } from '../services/api';

function decodeHtml(html: string): string {
Expand All @@ -16,8 +17,7 @@ function decodeHtml(html: string): string {

@autoinject()
export class ItemPage implements RoutableComponentCanActivate, RoutableComponentActivate {
item: Item | null = null;
comments: Item[] = [];
item: Trie<Item> | null = null;

private readonly api: HackerNewsApi;

Expand All @@ -30,16 +30,10 @@ export class ItemPage implements RoutableComponentCanActivate, RoutableComponent
}

async activate(params: any, routeConfig: RouteConfig): Promise<void> {
this.item = await this.api.fetchItem(params.id);
this.item = await this.api.fetchItemTrie(params.id);

if (this.item !== null) {
if (this.item.kids !== undefined && this.item.kids.length >= 1) {
this.comments = await this.api.fetchItems(this.item.kids);
}

if (this.item.title !== undefined) {
routeConfig.navModel!.setTitle(decodeHtml(this.item.title));
}
if (this.item !== null && this.item.value.title !== undefined) {
routeConfig.navModel!.setTitle(decodeHtml(this.item.value.title));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { autoinject } from 'aurelia-framework';
import {
NavigationInstruction,
Next,
PipelineStep
} from 'aurelia-router';

@autoinject()
export class ScrollToTopStep implements PipelineStep {
run(instruction: NavigationInstruction, next: Next): Promise<any> {
if (instruction.router.isNavigatingNew) {
Expand Down
44 changes: 33 additions & 11 deletions src/services/api.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import * as firebase from 'firebase/app';
import 'firebase/database';
import { Item } from '../models/item';
import { Trie } from '../models/trie';
import { User } from '../models/user';
import DataSnapshot = firebase.database.DataSnapshot;
import Reference = firebase.database.Reference;

const API_URL = 'https://hacker-news.firebaseio.com';
const API_VERSION = '/v0';
const API_VERSION = 'v0';
export const STORIES_PER_PAGE = 30;

async function valueOf(ref: Reference): Promise<any> {
let snapshot: DataSnapshot = await ref.once('value');
return snapshot.val();
}

export class HackerNewsApi {
private readonly db = firebase.initializeApp({ databaseURL: API_URL }).database().ref(API_VERSION);
private readonly db = firebase
.initializeApp({ databaseURL: API_URL })
.database()
.ref(API_VERSION);

private readonly users = this.db.child('user');
private readonly items = this.db.child('item');

fetchItemsOnPage(items: number[], page: number): Promise<Item[]> {
let start = (page - 1) * STORIES_PER_PAGE;
Expand All @@ -32,22 +45,31 @@ export class HackerNewsApi {
}

fetchItemIds(name: string): Promise<number[]> {
return this.fetch(name);
return valueOf(this.db.child(name));
}

fetchItem(id: number): Promise<Item | null> {
return this.fetch(`item/${id}`);
return valueOf(this.items.child(id.toString()));
}

fetchUser(id: string): Promise<User | null> {
return this.fetch(`user/${id}`);
return valueOf(this.users.child(id.toString()));
}

private fetch(path: string): Promise<any | null> {
return new Promise((resolve: (value: any) => void, reject: (reason: any) => void): void => {
this.db.child(path).once('value', (snapshot: DataSnapshot) => {
resolve(snapshot.val());
}, reject);
});
async fetchItemTrie(id: number): Promise<Trie<Item> | null> {
let value = await this.fetchItem(id);
if (value === null) {
return null;
}

let children: (Trie<Item> | null)[] = [];
if (value.kids !== undefined && value.kids.length > 0) {
children = await Promise.all(value.kids.map(kid => this.fetchItemTrie(kid)));
}

return {
value,
children
};
}
}

0 comments on commit e430323

Please sign in to comment.