How to Add a Blog to Your Angular App with ButterCMS
Posted by Taminoturoko Briggs on December 20, 2022
GSD
After a website has been planned and designed, all that’s left is to develop and implement it. This is something we will want to do as quickly as possible using reliable technologies so that we can move on to other important things.
Over the years, many great tools and platforms have been introduced to aid in the development process. Among these is ButterCMS, a headless content management system (CMS) that abstracts all the lengthy server-side code that would take days to complete. It does this by providing an easy-to-navigate dashboard where we can create and manage our content and a content delivery API to access that content using our preferred client framework and tools.
In this tutorial, we will learn about ButterCMS and how to use it with Angular to build a blog.
Table of contents
Why use Angular?
Angular is a mature front-end framework developed and maintained by Google that is used for building single-page applications (SPAs). It is known for having a great ecosystem, detailed documentation that clearly explains its concepts and practices, and support for building not only web apps but also progressive web apps (PWAs) and mobile apps.
Since Angular is built on TypeScript, which is a strict syntactical superset of JavaScript, applications built with it are more secure, more error-free, and easier to debug. Angular uses a component-based approach that allows the creation of individual self-contained pieces of code which can be reused throughout our app—speeding up the development process and creating code that is easier to maintain.
As mentioned earlier, Angular is mature, so while using it we can rest assured that all the tools and resources needed for creating our desired front-end experience are readily available.
Why use ButterCMS?
ButterCMS, being a headless CMS, acts as a repository for storing content which can then be accessed via its content API using native tools. Unlike a traditional monolithic CMS whose structure can limit your user experience, a headless CMS like ButterCMS gives us full control over the frontend by acting as your backend content repository, allowing us to create custom experiences for users. On top of that, ButterCMS is cloud-based and serverless, which means we don’t have to worry about scaling, security, or maintenance—saving us time to focus on more important problems.
Another great feature ButterCMS provides that we will be utilizing in this tutorial is its built-in blog engine. It includes a content structure for creating SEO-friendly blog posts and an interface for managing created posts.
Keep in mind that while we will be using the ButterCMS blog engine for this tutorial, you can also quickly create a custom blog page using ButterCMS page types.
Tutorial prerequisites
To follow along with this tutorial, you need the following:
- Knowledge of Angular
- Angular CLI installed
- Node.js v16 or greater installed on your system
- An active ButterCMS account (New accounts have a free trial period of 30 days.)
The code for this tutorial can be found in this GitHub repo.
Building a blog with Angular and ButterCMS
We will start by building the UI for our blog app. After that, we will set up ButterCMS, which will hold our blog content that will then be fetched and displayed in our app. Here is what we will be building in this tutorial:
Setting up Angular
To generate a new Angular app, enter the following commands in the terminal:
ng new angular-blog
When prompted, pick CSS for styling and add router to the app. The above will create a starter angular app called angular-blog.
Let’s clean up the app a bit, removing code that is not needed. Open the created app in a code editor and first modify src/app/app.component.html to the following:
<div></div>
Next, add the following styling to src/styles.css:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.wrapper {
width: 95%;
margin: 0 auto;
max-width: 1100px
}
body {
min-height: 100vh;
background-color: #f6fbff;
}
Now, let’s start the development server with the ng serve command.
Creating the blog’s UI
The blog will have a home page where all blog posts will be displayed and a details page for viewing individual posts. Let’s create the pages and the components to be used in them.
Open the terminal and make sure to enter the following commands in the root directory of the app. For pages:
for page in home detail; do ng g c --skip-tests=true "pages/${page}"; done
For a component:
ng g c --skip-tests=true "components/card"
After entering the above commands, here is what the folder structure of the app folder will look like:
app
┣ components
┃ ┗ card
┣ pages
┃ ┣ detail
┃ ┗ home
Now, let’s create the routes for our pages. Head over to app/app-routing.module.ts and add the following imports and modify the routes
variable to the following:
import { DetailComponent } from './pages/detail/detail.component';
import { HomeComponent } from './pages/home/home.component';
const routes: Routes = [
{path: '', component: HomeComponent},
{path: 'detail/:slug', component: DetailComponent}
];
Next, head over to app/app.component.html and modify it to the following:
<div>
<router-outlet></router-outlet>
</div>
Now, let’s create the pages, starting with the home page which will contain blog cards which display brief information about each blog post. Let’s first create the blog card component.
Head over to app/components/card/card.component.ts and add the following import:
import { Input } from '@angular/core';
Next, add the following properties in the component before the constructor:
@Input() slug!: string;
@Input() image!: string;
@Input() date!: string;
@Input() title!: string;
@Input() summary!: string;
@Input() authorImage!: string;
@Input() authorName!: string;
Above, we have declared the properties which will be passed down from the parent.
Next, modify the HTML file card.component.html to the following:
<div class="card" [routerLink]="['detail', slug]">
<img [src]="image" />
<div class="card__details">
<div>
<span class="mgn">{{ date }}</span>
<h3>{{ title }}</h3>
<p class="mgn">{{ summary }}</p>
</div>
<div class="card__author">
<img [src]="authorImage"/>
<span>{{ authorName }}</span>
</div>
</div>
</div>
Above, using the routerLink
directive, we have made the container div to navigate to the details page when clicked with the slug attached to the URL. We are attaching the slug because it will be used later to fetch and display the corresponding blog post on the details page.
Next, in the card.component.css file, add the following styles:
.mgn {
margin-bottom: 15px;
display: block;
}
.card {
max-width: 350px;
cursor: pointer;
box-shadow: 0 2px 6px gainsboro;
border-radius: 5px;
height: 100%;
display: flex;
flex-direction: column;
}
.card:hover {
transform: scale(1.01);
}
.card > img {
object-fit: cover;
width: 100%;
height: 200px;
}
.card__details {
padding: 10px;
display: flex;
flex-direction: column;
flex: 1;
justify-content: space-between;
}
.card__details span:first-child {
color: gray;
}
.card__details p {
font-size: 16px;
color: rgb(63 63 66);
}
.card__author img {
object-fit: cover;
width: 50px;
height: 50px;
border-radius: 100%;
vertical-align: middle;
}
.card__author span {
margin-left: 10px;
}
Now, let’s render the created component and design the home page. In the home page, we will be using the ngModel
directive for two-way binding for the search bar, so we will need to add the forms module to our app. To do this, head over to src/app/app.module.ts and add the following imports:
import { FormsModule } from '@angular/forms';
Then, modify imports
to look like the following:
imports: [
// ...
FormsModule,
],
Next, head over to the app/pages/home/home.component.ts file and define the following property which will hold the value entered in the search bar:
searchValue = ''
Next, let’s create the search bar and render the created card component by modifying the HTML file home.component.html to the following:
<div>
<div class="hero">
<div class="wrapper">
<header>
<h2>Angular Blog</h2>
<form>
<input
placeholder="Search blog"
[(ngModel)]="searchValue"
name="search-value"
/>
<button>Search</button>
</form>
</header>
<h2>Explore Angular based contents</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Mauris et porta erat, in sodales augue.
</p>
</div>
</div>
<div class="wrapper cardList">
<app-card
slug="slug of the post"
summary="Lorem ipsum dolor sit amet, consectetur adipiscing elit"
title="Example blog post"
image="https://images.unsplash.com/photo-1667586091163-e759ac830a1b?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1414&q=80"
authorImage="https://images.unsplash.com/photo-1650196186567-b42a5de15fb5?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1972&q=80"
authorName="Ray"
>
</app-card>
</div>
</div>
Right now we are passing dummy data to the card component just so we can see what we are building. Later on, we will replace the dummy data with data from ButterCMS.
Next, let’s add some styling. Add the following styles to home.component.css:
header {
display: flex;
align-items: center;
justify-content: space-between;
}
header h2 {
text-align: center;
margin: 20px 0;
}
form input, button {
height: 30px;
padding: 0 5px;
border: none;
}
form button:hover {
background-color: gainsboro;
}
.hero {
background-color: rgb(215,59,95);
height: 300px;
text-align: center;
color: white;
}
.hero .wrapper > h2 {
margin-top: 70px;
font-size: 28px;
}
.hero .wrapper p {
font-size: 18px;
}
.cardList {
display: grid;
margin-top: 20px;
place-content: center;
grid-template-columns: repeat(auto-fill, 350px);
gap: 15px;
}
Now, if we open our app in the browser, we will see the following:
Now, let’s work on the details page. For now, we will just add its styling. We will do the rest after we have fetched our data from ButterCMS. So, head over to app/page/detail/detail.component.css and add the following styling:
.blogDetail {
width: 95%;
max-width: 900px;
margin: 50px auto;
}
.blogDetail__head span:nth-child(2):after {
content: '.';
margin-left: 5px;
vertical-align: top;
}
.blogDetail__head span {
margin-left: 5px;
color: rgb(128, 128, 128);
}
.blogDetail > h1 {
margin-bottom: 15px;
}
.blogDetail > img {
margin: 15px 0;
}
.blogDetail img{
width: 100%;
}
.blogDetail .blogDetail__profileImg {
width: 30px;
height: 30px;
border-radius: 100%;
vertical-align: middle;
}
.blogDetail__body p {
color: rgb(62, 60, 60);
font-size: 18px;
}
.blogDetail__body h2, h3{
margin: 20px 0 5px;
}
.blogDetail__body img, video {
margin: 5px 0;
}
Setting up ButterCMS
Let’s use the blog engine to create a blog post to be displayed on the front-end and get the Read API Token which will enable us to work with ButterCMS.
Head over to the ButterCMS website and set up a user account if you do not have one already. After logging in, we will be directed to our dashboard where we can navigate to the blog engine by clicking on the Blog Posts icon in the sidebar.
We will see the following page:
Here, an example post has already been created for us which we can work with, but let’s create another to get familiar with how it’s done.
Click on the New Post button at the top-right of the page. On the next page, write your blog content with the WYSIWYG editor then fill out the rest of the inputs and click on the Publish button at the top-right of the page. For this tutorial, we can just use dummy data to fill out the fields.
Below is a GIF that demonstrates how to add a new blog post:
Now, to get our read API token, hover over your image at the top on the sidebar and click on Settings in the dropdown that appears and we will be taken to the following page where we will see the token:
Copy the token and store it in the src/environments/environment.ts file in our app, which should look like this:
export const environment = {
production: false,
readApiToken: "<your-read-api-token-here>"
};
Working with ButterCMS in our application
Now that everything has been set up, we are down to consuming our content created with the ButterCMS blog engine. For this, we can either use the content API or ButterCMS SDK, both of which function as stated in the API reference. For this tutorial, we will be using the content API.
We will start by displaying all our blog posts on the home page. Since we will be using HttpClient for sending requests, let’s add its module to our app. Head over to app/app.module.ts and add the following import:
import { HttpClientModule } from '@angular/common/http';
Next, modify the imports property by adding HttpClientModule
to it:
imports: [
//..
HttpClientModule
]
Next, head over to app/pages/home/home.component.ts and add the following import:
import { HttpClient } from '@angular/common/http';
import {environment} from '../../../environments/environment'
Next, modify HomeComponent
to the following:
export class HomeComponent implements OnInit {
blogPosts:any[] = []
searchValue = ''
constructor(private http: HttpClient) { }
ngOnInit(): void { this.http.get(`https://api.buttercms.com/v2/posts/?exclude_body=true&auth_token=${environment.readApiToken}`).subscribe((res: any) => {
this.blogPosts = res.data
})
}
}
Above, in the ngOnInit
method using HttpClient
, we fetch all blog posts from ButterCMS and store them in the blogPosts
property.
To display the blog posts, modify the home.component.html file to the following:
<div>
<h2>Angular Blog</h2>
<div class="wrapper cardList">
<app-card
*ngFor="let blogPost of blogPosts"
[slug]="blogPost.slug"
[date]="blogPost.created"
[summary]="blogPost.summary"
[title]="blogPost.title"
[image]="blogPost.featured_image"
[authorImage]="blogPost.author.profile_image"
[authorName]="blogPost.author.first_name + blogPost.author.last_name"
>
</app-card>
</div>
</div>
Now, the fetched blog post will be displayed, but we will notice that the displayed dates aren’t in the right format. To fix this, head over to app/components/card/card.component.ts and modify the ngOnInit
method to the following:
ngOnInit(): void {
this.date = new Date(this.date).toISOString().split('T')[0]
}
With this, when we open our app in the browser, we will see the following:
Now, let’s make the search bar work where we will use the text entered in the search bar to filter posts by their title and display the results. For this, we will need to create a property that will hold the filtered post and be used to display the UI rather than directly filtering the fetched posts in the blogPosts
property so that no post will be lost even after searching.
First, add the following property in HomeComponent
:
filteredBlogs: any[] = []
Since the filteredBlogs
property will be used to display the UI, it needs to contain the fetched blog post when the app loads. To do this, modify the ngOnInit
method to the following:
ngOnInit(): void {
this.http.get(`https://api.buttercms.com/v2/posts/?exclude_body=true&auth_token=${environment.readApiToken}`).subscribe((res: any) => {
this.blogPosts = res.data
this.filteredBlogs = this.blogPosts
})
}
Next, let’s create the search method by adding the following lines of code after the constructor
:
handleSearch() {
this.filteredBlogs = this.blogPosts.filter((blog) => (
blog.title.trim().toLocaleLowerCase()
.includes(this.searchValue.trim().toLocaleLowerCase())
))
}
Now let’s call the above method when the search form is submitted and display the UI with the filteredBlogs
property.
Head over to the HTML file home.component.html and modify the opening form tag to the following:
<form (submit)="handleSearch()">
Next, modify the rendered app-card
component to look like the following:
<app-card
*ngFor="let blogPost of filteredBlogs"
// ...
>
</app-card>
With this, the search bar should now be working.
Now, let’s work on displaying an individual blog post on the details page. Head over to app/pages/detail/detail.component.ts and add the following imports:
import { ActivatedRoute } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import {environment} from '../../../environments/environment'
Next, modify DetailComponent
to the following:
export class DetailComponent implements OnInit {
blogPost:any
date!: string
constructor(
private route: ActivatedRoute,
private http: HttpClient
) { }
ngOnInit(): void {
const slug = this.route.snapshot.paramMap.get('slug') this.http.get(`https://api.buttercms.com/v2/posts/${slug}/?auth_token=${environment.readApiToken}`)
.subscribe((res: any) => {
this.blogPost = res.data
this.date = new Date(this.blogPost.created).toISOString().split('T')[0]
})
}
}
Above, after injecting the required services by modifying the constructor in the ngOnInit()
method, we get the slug which is attached to the details page URL and use it to fetch the corresponding blog post from ButterCMS. Then, we store the result in the blogPost
property.
Now let’s display the blog post on the page. Modify the detail.component.html file to the following:
<div class="blogDetail">
<h1>{{blogPost?.title}}</h1>
<div class="blogDetail__head">
<img [src]="blogPost?.author.profile_image" class="blogDetail__profileImg" />
<span>{{blogPost?.author.first_name + ' ' + blogPost?.author.last_name}}</span>
<span>{{date}}</span>
</div>
<img [src]="blogPost?.featured_image"/>
<div class="blogDetail__body" [innerHTML]="blogPost?.body"></div>
</div>
With this, when we navigate to the details page by clicking on a blog card, we will see the content of that blog post. But there are a few things that still need fixing, like the styling not being applied even though we added it earlier while building the UI and the video under the Blog Engine Demo heading not displaying. This is a result of Angular’s view encapsulation and HTML sanitization being carried out on HTML inserted with the innerHTML
property, as was done in the above code.
To fix this, there are a few tweaks we need to make. In the detail.component.ts file, add the following imports:
import {ViewEncapsulation} from '@angular/core'
import {DomSanitizer, SafeHtml} from '@angular/platform-browser';
Next, to make the styling work, add the following property to the @Component
decorator:
@Component({
// ..
encapsulation: ViewEncapsulation.None,
})
Next, to display the video under the Blog Engine Demo heading, we will need to bypass Angular’s sanitization, which deems the iframe
that inserts the video as unsafe. To do this, first add the following property to the component and modify the constructor to inject DomSanitizer
:
html!: SafeHtml
constructor(
//…
private sanitizer: DomSanitizer
) { }
Next, modify the subscribe method to the following:
subscribe((res: any) => {
this.blogPost = res.data
this.date = new Date(this.blogPost.created).toISOString().split('T')[0]
this.html = this.sanitizer.bypassSecurityTrustHtml(this.blogPost.body);
})
Above, we bypass the sanitization by supplying the body of the blog content to the bypassSecurityTrustHtml
method, then store the result in the html
property.
Now we need to display the value of the html property rather than directly displaying the body of the blog’s content. In the detail.component.html file, modify the div
with the innerHTML
property to the following:
<div class="blogDetail__body" [innerHTML]="html"></div>
With this, the example post on the details page should now look fine:
Final thoughts
With a platform like ButterCMS, we can get our blog up and running in a few hours using its blog engine rather than taking several days writing lengthy server-side code. In this tutorial, we have learned about ButterCMS and how to use it to build a blog in Angular. I hope you enjoyed this tutorial, and be sure to leave a comment if you have any questions.
ButterCMS is the #1 rated Headless CMS
Related articles
Don’t miss a single post
Get our latest articles, stay updated!
Taminoturoko Briggs is a software developer and technical writer with sound knowledge of different web technologies. His core languages include JavaScript and Python.