• This is a part three of the tutorial on building Angular theme for Blogifier.
  • Source files for this tutorial hosted in Github repository
  • Here is first and second part of the tutorial if you missed them

Adding Blog Service

To streamline development, I'm going to use data service from existing Blogifier theme. So I go and grab it from here and copy to src/app/core. One thing to fix right away is to modify environment files to include API endpoint required by service I just imported. Because our theme uses public APIs, we can point it to any Blogifier instance. If there any specific needs, I can run Blogifier locally and point my theme to this local instance. But for generic things I can just point it to http://blogifier.net and use that demo site to pull posts from there. This way I don't have to care about anything on the back-end.

There two environment files, one for development and it will be used whenever I run application in VS Code. The other is going to be used when I build application with build --prod to deliver it to Blogifier and run as a theme.

// environment.ts
export const environment = {
  production: false,
  apiEndpoint: 'http://blogifier.net'
};

// environment.prod.ts
export const environment = {
  production: true,
  apiEndpoint: ''
};

Here I use blogifier.net for development, and when built for production and deployed empty string indicates that theme is going to look for API endpoints on the same server where it deployed.

And now that we going to use HTTP client to call APIs, application module needs reference to it.

...
import { HttpClientModule } from '@angular/common/http';

@NgModule({
   ...
   imports: [
   ...
   HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})

Using Data Service

All ready to call data service and pull posts list from the Blogifier. Here is home.component.ts updated to use data service on page load.

import { Component, OnInit } from '@angular/core';
import { BlogService, IPostList } from '../core/blog.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
  public postList: IPostList;
  constructor(private blogService: BlogService) { }

  ngOnInit() {
    this.blogService.getPosts().subscribe(
      result => { this.postList = result; }
    );
  }
}

And home.component.html updated to dynamically bind post data to markup

<div *ngIf="postList" class="row">
  <div class="col-lg-8 col-md-10 mx-auto">
    <div *ngFor="let post of postList.posts" class="post-preview">
      <a href="posts/{{post.slug}}">
        <h2 class="post-title">{{post.title}}</h2>
        <h3 [innerHTML]="post.description" class="post-subtitle"></h3>
      </a>
      <p class="post-meta">Posted by <a href="#">
        Start Bootstrap</a> on September 24, 2019</p>
    </div>
  </div>
</div>

The page in the browser now should display post list from the demo Blogifier site, something like this

data/rtur/2019/8/ang-tut3-posts.png

Same goes for single post page, with addition of handling post slug passed via query string from the post list page. Angular uses ActiveRoute that can be injected in the constructor and used to check for query string values. So its easy to get slug value from the route and pass it to the service in the getPost(slug) method.

Another thing used here is environment variable. It is a little hack to display pictures from Blogifier in the page headers.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { environment } from '../../environments/environment';
import { BlogService, IPostModel } from '../core/blog.service';

@Component({
  selector: 'app-posts',
  templateUrl: './posts.component.html',
  styleUrls: ['./posts.component.css']
})
export class PostsComponent implements OnInit {
  postModel: IPostModel;
  postCover: string;
  constructor(private blogService: BlogService, 
      private route: ActivatedRoute) { }

  ngOnInit() {
    var slug = this.route.snapshot.paramMap.get('slug');
    this.blogService.getPost(slug).subscribe(
      result => { 
        this.postModel = result;
        this.postCover = environment.apiEndpoint + '/' + 
            this.postModel.post.cover;
      }
    );
  }
}

The posts.component.html is straight forward, binding values from Angular model inside double curly brackets. Some points to notice are that you should check for model object existence before bind it with *ngIf and to bind HTML content innerHTML property used instead of brackets. Also date formatted with standard Angular pipe to prettify output.

<!-- Page Header -->
<header *ngIf="postModel" class="masthead" [ngStyle]="{'background-image':'url('+postCover+')'}">
  <div class="overlay"></div>
  <div class="container">
    <div class="row">
      <div class="col-lg-8 col-md-10 mx-auto">
        <div class="post-heading">
          <h1>{{postModel.post.title}}</h1>
          <h2 [innerHTML]="postModel.post.description" class="subheading"></h2>
          <span class="meta">Posted by
            <a href="?author={{postModel.post.author.appUserName}}">{{postModel.post.author.displayName}}</a>
            {{postModel.post.published | date:'longDate'}}</span>
        </div>
      </div>
    </div>
  </div>
</header>
<!-- Post Content -->
<article>
  <div class="container">
    <div *ngIf="postModel" class="row">
      <div [innerHTML]="postModel.post.content" class="col-lg-8 col-md-10 mx-auto">
      </div>
    </div>
  </div>
</article>

Single post should look similar to the picture below now.

data/rtur/2019/8/ang-tut3-single-post.png

This is a bulk of the work building Angular theme, what left is small things to finalize and improve here and there. I'll go over it in the next, last in this series tutorial. When done, I'll deploy it to Blogifier to complete the job.