Angular 2 — HackerNews clone : Dynamic Components, Routing params and Refactor

This is the fourth post in the series of building a HackerNews clone with Angular 2, Angular Material and Firebase. In this post we will refactor our top stories, newest components from multiple components into single feeds component which will handle all feeds in HackerNews like best stories, top stories, asks, jobs, etc., and then we will switch service calls according to the current activated route. We will create a single item component which will dynamically load specific components like Story, Poll, etc., using Angular 2’s Dynamic Component Loader. In this blog we will only build one story component.

Previous posts :

  1. Setting up Angular2 and Angular Material with Angular CLI.
  2. Components and Routers — Initial Setup
  3. Setting up firebase and realtime updates

Steps we will be following for this post

  1. Add all feeds service calls to the HackerNewsService
  2. Create a feeds component and a service config mapping
  3. Routing updates and service switching in the feeds component
  4. Create a component for different item types. Only one story component for this post
  5. Create a dynamic component loader to switch components
  6. Create a comment component to display a tree of comments

#STEP1

According to HackerNews API we can get a list of best stories, top stories, best stories, asks and jobs. So we are going to add all of these calls to the HackerNewsService

#STEP2

Generate a feeds module, containers folder and a feeds component

ng g module feeds
mkdir src/app/feeds/containers
ng g components feeds/containers/feeds

We are going to create a config file for feeds which contains the mapping for routes and our service methods.

#STEP3

For the feeds component, we will be reusing our hn-story component from the previous post to display each item on the list.

Angular Router provides a service called ActivatedRoute which exposes details about the current active route. We will utilize this to determine which route is currently active and switch our service calls picking from our feeds config accordingly. So here we are using routeConfig.path which has a mapping method and limit from the feed config mapping.

We will update our app.routing.ts to reflect the routing changes and addition.

#STEP4

HackerNews has different item types like Story, Poll, Job etc., which will have different user experiences associated with it. Below will be our flow for a single item.

  1. User clicks/tap on one of the feed item
  2. Navigate the user to /item/:id which loads the ItemComponent
  3. ItemComponent will make the service call to get details about the item
  4. Then determine type of the item and load the appropriate component dynamically into the view

We will be creating anitem module and other components and containers within the module that will be utilized. We have covered the creation process in the previous blog posts, so I am just going to display the screenshot of the structure below.

In this post we will be focusing on three things from above item comment and story.

As discussed before we will be using a dynamic component loader (docs). There are few steps in the setup process for us to load components into view dynamically.

During the registration of the components into item.module.ts — we need to add them to an array entryComponents. Since the components are dynamically loaded, they are not referenced in any of the templates. During the compilation process, when the components are not part of the template, the angular compiler doesn’t create a factory for it. So we have to tell the compiler that we have components that are going to be loaded dynamically and will not be part of the templates so it can create factories for them. So below in item.module.ts we are going to register our StoryComponent, PollComponent and JobComponent as entry components in addition to adding them to the declaration.

Below is our item.component.html — it is pretty straight forward as it has a <template> tag with a local variable #dynamicItemView. This is the section that we will be loading our dynamic components into. You can read about local variables here, we will be using the docs reference of Parent calls the view child using local variables.

So a lots going on here, lets break it down.

Line no: 15 We need a reference to the location in the dom where we are going to load our component. We use @ViewChild decorator with our local variable dynamicItemView and then read ViewContainerRef. We are requesting the read ViewContainerRef since it is a representation of a container for which views can be attached.

Line no: 17 In our constructor we are injecting our dependencies ActivatedRoute, HackerNewsService and ComponentFactoryResolver. We will see the utilization of each one of them below.

Line no: 21: Activated Route exposes the current routes params as an observable. We are going to observe the params and look for the id property which will be used to get details from the HackerNewsService. When the service emits a value we are passing that emitted item to loadComponent

Before we get into theloadComponent function, we will take a quick jump to line no: 37, each of the item has a type property which indicates what kind of item it is, such as story, poll, job etc. getItemMapping contains a simple switch statement which returns the appropriate component, depending on the item type passed.

LoadComponent method is where the magic happens on loading our components dynamically into the view.

#1 We are creating a factory from our component which is returned from getItemMapping component.

#2 We are cleaning up our itemViewContainer

#3 We are going to create a component on the container using createComponent method and are passing that into our component factory.

#4 At last we are assigning our item to the data property on our component instance.

We will build our story and comment component now. Our story component is a dumb/presentational component. We will be passing the data it needs in order for it to be rendered (#4 in the previous section).

All we are doing here is just displaying the story title, we will add the comment component later.

We need to update the routing and feed item for navigation to the Item Component.

  //app.routing.ts
{
path: 'item/:id',
component: ItemComponent
}
  //feed-item.component.html - line no : 2
<md-card-content [routerLink]="['/item', item.id ]">

Now we will develop our comment component, hacker news stories can have nested comments as well. The comment component takes an id as an input and calls the HackerNewsService to get the comment. There is a visible boolean which is used to hide the current comment text and any comment nested under it, so the user can fold up comments as needed.

Below is the comment template. Most of it pretty straight forward, lets focus on couple of things here.

Line no: 5 Has a click handler which toggles the visible boolean which is toggling visibility of the div in line no: 7.

Line no: 9 . We display our nested comments using the same hn-comment component.

Since we are using innerHTML to represent our comment content, the component styles will not be reflected. We need to use a special selector to force the style down into the inner html/deep/ docs. In our comments section we might have a pre tag, which we will add some styles to comment.component.scss

:host /deep/ pre {
white-space: pre-wrap;
line-height: 1.5;
}

We will add the comment component to our story template and few properties to view.

App is deployed at https://hackernews-clone.firebaseapp.com/

Was unable to upload GIF on to medium, but here is the link http://gph.is/2lhs1lO