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 :
- Setting up Angular2 and Angular Material with Angular CLI.
- Components and Routers — Initial Setup
- Setting up firebase and realtime updates
Steps we will be following for this post
- Add all feeds service calls to the HackerNewsService
- Create a feeds component and a service config mapping
- Routing updates and service switching in the feeds component
- Create a component for different item types. Only one story component for this post
- Create a dynamic component loader to switch components
- 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.
- User clicks/tap on one of the feed item
- Navigate the user to
/item/:id
which loads theItemComponent
ItemComponent
will make the service call to get details about the item- 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