How to Build a Serverless, CMS-powered Angular Application

Jake Lumetta
Jake Lumetta
Share

Angular has taken off in popularity and is in widespread use. Developed and maintained by Google engineers, Angular has found a place all across dynamic web applications and is an increasingly in-demand platform.

Angular offers the advantages of a large and enthusiastic community and outstanding MVC that doesn’t require developers to spend valuable time writing code to put multiple MVC components back together again. In short, Angular is a robust and comprehensive web application framework for front-end development that is unit-testing ready, making it the tool of choice for many developers.

If you’re using Angular, you may run into the need for content management capability — a blog being one example. Adding a CMS to an Angular app may seem daunting, especially if you’re trying to integrate it into a traditional CMS like WordPress, but there’s a new breed of API-based CMS that greatly simplifies things. ButterCMS is one example of a SaaS-based headless CMS that provides a hosted CMS dashboard and content API that you query from your Angular application. This means you don’t need to spin up any new infrastructure to add a CMS to your Angular app.

This tutorial will demonstrate how to build a CMS-powered Angular application that has marketing pages (customer case studies), a blog, and FAQ, all powered via an API. No servers needed!

Installation

First, you’ll get started by installing the Angular CLI.

npm install -g @angular/cli</td>

Set up a new Angular project using Angular CLI. By default, Angular CLI uses CSS styling, so adding the --style=scss flag tells Angular CLI to use SCSS instead:

ng new hello-buttercms-project --style=scss
cd hello-buttercms-project

Install Angular Material and Angular Material related package:

npm install --save @angular/material @angular/cdk
npm install --save @angular/animations

Install ButterCMS. Run this in your command line:

npm install buttercms --save

Butter can also be loaded using a CDN:

<script src="https://cdnjs.buttercms.com/buttercms-1.1.1.min.js"></script>

Quickly Get Started

Open the project in your code editor of choice. Under src/app create a directory called _services.

We create a file called butterCMS.service.js. This allows us to have your API Token in one place and not accidentally alter it.

import * as Butter from 'buttercms';

export const butterService = Butter('b60a008584313ed21803780bc9208557b3b49fbb');

You’ll import this file into any component we want to use ButterCMS.

For a Quickstart, go to src/app/hello-you/hello-you.component.ts and import butterService:

import {butterService} from '../_services';

Inside the HelloYouComponent create methods:

fetchPosts() {
  butter.post.list({
    page: 1,
    page_size: 10
  })
  .then((res) => {
    console.log('Content from ButterCMS')
    console.log(res)
  })
}

Now call this method when the component is loaded by adding it to the OnInit lifecycle hook:

ngOnInit() {
  this.fetchPosts();
}

This API request fetches your blog posts. Your account comes with one example post, which you’ll see in the response.

Next, create another method to retrieve the Homepage Headline Content Field:

fetchHeadline() {
  butter.content.retrieve(['homepage_headline'])
    .then((res) => {
      console.log('Headline from ButterCMS')
      console.log(res)
    })
}

Add this method to the OnInit lifecycle hook.

ngOnInit() {
  this.fetchPosts();
  this.fetchHeadline();
}

This API request fetches homepage headline content. You can set up your own custom content fields to manage any kind of content you need.

Add Marketing Pages

Setting up CMS-powered pages is a simple, three-step process:

  1. Define the Page Type
  2. Create a page
  3. Integrate into your application

Define Page

First, create a Page Type to represent your Customer Case Study pages. Next, define the fields you want for your customer case studies. With your Page Type defined, you can now create the first case study page. Specify the name and URL of the page, and then populate the content of the page.

With your page defined, the ButterCMS API will return it in JSON format like this:

{
    "data": {
        "slug": "acme-co",
        "fields": {
            "facebook_open_graph_title": "Acme Co loves ButterCMS",
            "seo_title": "Acme Co Customer Case Study",
            "headline": "Acme Co saved 200% on Anvil costs with ButterCMS",
            "testimonial": "<p>We've been able to make anvils faster than ever before! - <em>Chief Anvil Maker</em></p>\r\n<p><img src=\"https://cdn.buttercms.com/NiA3IIP3Ssurz5eNJ15a\" alt=\"\" caption=\"false\" width=\"249\" height=\"249\" /></p>",
            "customer_logo": "https://cdn.buttercms.com/c8oSTGcwQDC5I58km5WV",
        }
    }
}

This guide uses the Angular framework and Angular CLI to generate all our components and package our application.

Let’s get to the code.

Create a New Project

ng new buttercms-project --style=scss
cd buttercms-project
npm install --save @angular/material @angular/cdk
npm install --save @angular/animations
npm install -S buttercms
ng serve

Your localhost:4200 should be ready to serve your Angular page.

Create TypeScript to Export ButterCMS Service

Under src/app, create a directory called _services. Create a file called butterCMS.service.js.

import * as Butter from 'buttercms';
export const butterService = Butter('your_api_token');

Update the Component Routes

These components are generated by Angular CLI using:

ng g component <my-new-component>

Under src/app, create a file called app-routing.module.ts:

import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {CustomerComponent} from './customer/listing/customer.listing.component';
import {FaqComponent} from './faq/faq.component';
import {BlogPostComponent} from './blog-post/listing/blog-post.component';
import {HomeComponent} from './home/home.component';
import {CustomerDetailsComponent} from './customer/details/customer.details.component';
import {BlogPostDetailsComponent} from './blog-post/details/blog-post.details.component';
import {FeedComponent} from './feed/feed.component';
import {HelloYouComponent} from './hello-you/hello-you.component';

const appRoutes: Routes = [
    {path: 'customer', component: CustomerComponent},
    {path: 'customer/:slug', component: CustomerDetailsComponent},
    {path: 'faq', component: FaqComponent},
    {path: 'blog', component: BlogPostComponent},
    {path: 'blog/:slug', component: BlogPostDetailsComponent},
    {path: 'rss', component: FeedComponent},
    {path: 'hello-you', component: HelloYouComponent},
    {path: 'home', component: HomeComponent},
    {path: '**', redirectTo: 'home'}
];

@NgModule({
    imports: [RouterModule.forRoot(appRoutes)],
    exports: [RouterModule]
})
export class AppRoutingModule {
}

Set Up the Customers List Page

Under apps/customer type:

ng g component listing

In the file apps/customer/listing/customer.listing.component.ts:

  1. Import butterService
  2. In the OnInit hook, use butterService to get the list of customers
  3. Store results in the pages variable and the markup (HTML) will be updated with the data.
import {Component, OnInit} from '@angular/core';
import {butterService} from '../../_services';

@Component({
    selector: 'app-customer',
    templateUrl: './customer.listing.component.html',
    styleUrls: ['./customer.listing.component.scss']
})

export class CustomerComponent implements OnInit {
  public pages: any[];
  constructor() { }

  ngOnInit() {
    butterService.page.list('customer_case_study')
      .then((res) => {
        this.pages = res.data.data;
      });
  }
}

Display the results in customer.listing.component.html:

<mat-card>
  <mat-card-title class="page-title">Customers</mat-card-title>
  <mat-divider></mat-divider>
  <mat-card-content class="page-body">
      <mat-card *ngFor="let page of pages">
          <mat-card-title>
              <div class="container">
                  <a [routerLink]="[page.slug]">
                      <div fxLayout="row" fxLayout.xs="column"
                           fxFlex class="content">
                          <div class="blocks">
                              <img src="{{page.fields.customer_logo}}" alt="{{page.fields.seotitle}}" height="64"
                                   width="64"/>
                          </div>
                          <div class="blocks">
                              {{page.fields.headline}}
                          </div>
                      </div>
                  </a>
              </div>
          </mat-card-title>
      </mat-card>
  </mat-card-content>
  <mat-divider></mat-divider>
  <mat-card-footer>
      <div class="page-footer">
          <mat-icon>whatshot</mat-icon>
      </div>
  </mat-card-footer>
</mat-card>

Set Up the Customer Detail Page

Under apps/customer, type ng g component details.

apps/customer/details/customer.details.component.ts

Create the customer page

  1. Import butterService
  2. In the OnInit hook, use butterService to get the customer page given the slug in the URL path
  3. Store results in a page variable and the markup (HTML) will be updated with the customer data.
import {Component, OnInit} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {ActivatedRoute} from '@angular/router';
import {butterService} from '../../_services';
import {map, take} from 'rxjs/operators';

@Component({
  selector: 'app-customer-details',
  templateUrl: './customer.details.component.html',
  styleUrls: ['./customer.details.component.scss']
})

export class CustomerDetailsComponent implements OnInit {
  constructor(protected route: ActivatedRoute) { }

  protected slug$: Observable<string>;
  public page: any;

  ngOnInit() {
    this.slug$ = this.route.paramMap
      .pipe(
        map(params => (params.get('slug')))
      );

    this.slug$.pipe(
      take(1))
      .subscribe(slug => {
        butterService.page.retrieve('customer_case_study', slug)
          .then((res) => {
            this.page = res.data.data;
          }).catch((res) => {
          console.log(res);
        });
      });
  }
}

Display the results in customer.details.component.html.

<mat-card>
  <div class="container">
    <div fxLayout="column" class="details">
      <div class="blocks">
        <img src="{{page.fields.customer_logo}}" alt="" height="124" width="124"/>
      </div>

      <h1 class="blocks">
        {{page.fields.headline}}
      </h1>
      <h3 class="is-size-3">Testimonials</h3>
      <div [innerHTML]="page.fields.testimonial"></div>
      <div [innerHTML]="page.fields.body"></div>
    </div>
  </div>
</mat-card>

You can now navigate to the Customer Page via the list of all Customer Pages or directly via the URL.

Add a Knowledge Base

Set up content fields

Let’s suppose you want to add a CMS to a static FAQ page with a title and a list of questions with answers.

Making your content dynamic with Butter is a two-step process:

  1. Set up custom content fields in Butter
  2. Integrate the fields into your application.

To set up custom content fields, first sign in to the Butter dashboard.

Create a new workspace or click on an existing one. Workspaces let you organize content fields in a friendly way for content editors and have no effect on development or the API. For example, a real estate website might have a workspace called Properties and another called About Page.

Once you’re in a workspace, click the button to create a new content field. Choose the Object type and name the field FAQ Headline.

After saving, add another field, but this time choose the Collection type and name the field FAQ Items.

On the next screen, set up two properties for items in the collection.

Now go back to your workspace and update your heading and FAQ items.

Integrate Your App

Create FAQ Component

Under apps, type ng g component faq.

apps/faq/faq.component.ts

Set up the onInit hook to load the FAQ

import {Component, OnInit} from '@angular/core';
import {butterService} from '../_services';

@Component({
  selector: 'app-faq',
  templateUrl: './faq.component.html',
  styleUrls: ['./faq.component.scss']
})

export class FaqComponent implements OnInit {
  constructor() {}

  public faq: any = {
      items: [],
      title: 'FAQ'
  };

  ngOnInit() {
    butterService.content.retrieve(['faq_headline', 'faq_items'])
      .then((res) => {
        console.log(res.data.data);
        this.faq.title = res.data.data.faq_headline;
        this.faq.items = res.data.data.faq_items;
      });
  }
}

Display the result

<mat-card>
  <mat-card-title class="page-title"></mat-card-title>
  <mat-divider></mat-divider>
  <mat-card-content class="page-body">
    <mat-card *ngFor="let item of faq.items">
      <mat-card-content>
        <h3>
          {{item.question}}
        </h3>
        <div>
          {{item.answer}}
        </div>
      </mat-card-content>
    </mat-card>
  </mat-card-content>
  <mat-divider></mat-divider>
  <mat-card-footer>
    <div class="page-footer">
      <mat-icon>whatshot</mat-icon>
    </div>
  </mat-card-footer>
</mat-card>

The values entered in the Butter dashboard will immediately update the content in our app.

Blogging

To display posts, we create a simple /blog route in your app and fetch blog posts from the Butter API, as well as a /blog/:slug route to handle individual posts.

See our API reference for additional options, such as filtering by category or author. The response also includes some metadata we’ll use for pagination.

Set Up the Blog Home Page

Under apps/blog-post, type ng g component listing.

apps/blog-post/listing/blog-post.listing.component.ts

Update the component to get all posts:

  1. import butterService
  2. get all posts onInit
import {Component, OnInit} from '@angular/core';
import {butterService} from '../../_services';

@Component({
  selector: 'app-blog-post',
  templateUrl: './blog-post.component.html',
  styleUrls: ['./blog-post.component.scss']
})
export class BlogPostComponent implements OnInit {
  public posts: any[];

  constructor() { }

  ngOnInit() {
    butterService.post.list({
      page: 1,
      page_size: 10
    }).then((res) => {
      console.log(res.data)
      this.posts = res.data.data;
    });
  }
}

Display the result:

<mat-card>
  <mat-card-title class="page-title">Blog Posts</mat-card-title>
  <mat-divider></mat-divider>
  <mat-card-content class="page-body">
    <mat-card *ngFor="let post of posts">
      <mat-card-title>

        <a [routerLink]="[post.slug]">
          <div class="container">
            <div fxLayout="row" fxLayout.xs="column"
               fxFlex class="content">
              <div class="blocks">
                <img *ngIf="post.featured_image" src="{{post.featured_image}}" height="64" width="64"/>
              </div>
              <div class="blocks">
                {{post.title}}
              </div>
            </div>
          </div>
          <div class="container">
            <div fxLayout="column" class="summary">
              <div [innerHTML]="post.summary"></div>
            </div>
          </div>
        </a>
      </mat-card-title>
    </mat-card>
  </mat-card-content>
  <mat-divider></mat-divider>

  <mat-card-footer>
    <div class="page-footer">
      <mat-icon>whatshot</mat-icon>
    </div>
  </mat-card-footer>
</mat-card>

Set up Blog Post Page

Under apps/blog-post, type ng g component details.

apps/blog-post/details/blog-post.details.component.ts

To show the single post:

  1. Import butterService
  2. In the OnInit hook, use butterService to get the blog-post post given the slug in the URL path
  3. Store results in the post variable and the markup (HTML) will be updated with the customer data.
import {Component, OnInit, ViewEncapsulation} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {ActivatedRoute} from '@angular/router';
import {butterService} from '../../_services';
import {map, take} from 'rxjs/operators';


@Component({
    selector: 'app-blog-post-details',
    templateUrl: './blog-post.details.component.html',
    styleUrls: ['./blog-post.details.component.scss'],
    encapsulation: ViewEncapsulation.None
})
export class BlogPostDetailsComponent implements OnInit {

    constructor(protected route: ActivatedRoute) {
    }

    protected slug$: Observable<string>;
    public post = {
        meta: null,
        data: null
    };

    ngOnInit() {
        this.slug$ = this.route.paramMap
            .pipe(
                map(params => (params.get('slug')))
            );

        this.slug$.pipe(
            take(1))
            .subscribe(slug => {
                butterService.post.retrieve(slug)
                    .then((res) => {
                        this.post = res.data;
                    }).catch((res) => {
                    console.log(res);
                });
            });
    }
}

Display the result:

<mat-card>
  <div class="container">
    <div fxLayout="column" class="blog-details">
      <div class="container">
        <div fxLayout="row">
          <h1 class="blocks">
            {{post.data.title}}
          </h1>
          <div *ngIf="post.meta.previous_post"><a [routerLink]="post.meta.previous_post"><</a></div>
          <div *ngIf="post.meta.next_post"><a [routerLink]="post.meta.next_post">></a></div>
        </div>
        <h4>
          {{post.data.author.first_name}} {{post.data.author.last_name}}
        </h4>
        <div class="post-body" [innerHTML]="post.data.body"></div>
      </div>
    </div>
  </div>
</mat-card>

Now your app has a working blog that can be updated easily in the ButterCMS dashboard.

Categories, Tags, and Authors

Use Butter’s APIs for categories, tags, and authors to feature and filter content on your blog.

List all categories and get posts by category

Call these methods on the onInit() lifecycle hook:

methods: {
  ...
  getCategories() {
    butter.category.list()
      .then((res) => {
        console.log('List of Categories:')
        console.log(res.data.data)
      })
  },
  getPostsByCategory() {
    butter.category.retrieve('example-category', {
        include: 'recent_posts'
      })
      .then((res) => {
        console.log('Posts with specific category:')
        console.log(res)
      })
  }
},
created() {
  ...
  this.getCategories()
  this.getPostsByCategory()
}

Wrapping It Up

Congrats! You’ve successfully turned your static Angular application into a CMS-powered app using content APIs, thereby maintaining a serverless architecture. Your development team can take advantage of the time-saving aspects of Angular, and you’ve saved even more time by using a serverless CMS.

Frequently Asked Questions (FAQs) on Building a Serverless CMS Powered Angular Application

What are the benefits of using a serverless CMS for my Angular application?

A serverless CMS offers several advantages for your Angular application. Firstly, it eliminates the need for server management, which can significantly reduce operational costs and complexity. Secondly, it provides automatic scaling, meaning your application can handle any amount of traffic without manual intervention. Thirdly, it offers built-in high availability and fault tolerance, ensuring your application is always up and running. Lastly, it allows you to pay only for the compute time you consume, making it a cost-effective solution.

How does a serverless CMS compare to traditional CMS?

Unlike traditional CMS, a serverless CMS does not require a server to operate. This means you don’t have to worry about server maintenance, updates, or security patches. Additionally, a serverless CMS is typically more scalable and flexible than a traditional CMS. It can easily handle traffic spikes and allows you to add or remove resources as needed. However, a serverless CMS may require more technical knowledge to set up and manage.

Can I use a serverless CMS for large-scale applications?

Yes, a serverless CMS is well-suited for large-scale applications. It provides automatic scaling, which means it can handle large amounts of traffic without any manual intervention. Additionally, it offers high availability and fault tolerance, ensuring your application is always available to users. However, it’s important to note that a serverless CMS may require more technical expertise to set up and manage compared to a traditional CMS.

How secure is a serverless CMS?

A serverless CMS is typically very secure. It eliminates the need for server management, which reduces the risk of server-related security issues. Additionally, many serverless CMS providers offer built-in security features, such as encryption and access control. However, like any technology, a serverless CMS is not immune to security threats. Therefore, it’s important to follow best practices for security, such as regularly updating your software and monitoring your application for suspicious activity.

What are some popular serverless CMS options?

There are several popular serverless CMS options available today. Some of the most popular ones include Contentful, ButterCMS, and Webiny. These platforms offer a range of features, such as automatic scaling, high availability, and built-in security. They also provide APIs that allow you to integrate your serverless CMS with other services and technologies.

How do I choose the right serverless CMS for my Angular application?

Choosing the right serverless CMS for your Angular application depends on several factors. These include your technical expertise, budget, and specific needs. You should consider the features offered by the CMS, such as automatic scaling, high availability, and built-in security. Additionally, you should consider the CMS’s compatibility with Angular and other technologies you’re using.

Can I migrate my existing Angular application to a serverless CMS?

Yes, it’s possible to migrate your existing Angular application to a serverless CMS. However, the process can be complex and requires careful planning. You’ll need to consider factors such as data migration, application architecture, and compatibility with the serverless CMS. It’s recommended to work with a professional or consult with the CMS provider to ensure a smooth migration.

What are the costs associated with using a serverless CMS?

The costs of using a serverless CMS can vary depending on the provider and your specific needs. Most providers offer a pay-as-you-go pricing model, meaning you only pay for the compute time you consume. However, there may be additional costs for features such as data storage, data transfer, and premium support. It’s important to carefully review the pricing details before choosing a serverless CMS.

Do I need to know any specific programming languages to use a serverless CMS?

Most serverless CMS platforms are language-agnostic, meaning they can work with any programming language. However, some knowledge of JavaScript and Angular may be beneficial when using a serverless CMS for an Angular application. Additionally, understanding how to work with APIs can be helpful, as many serverless CMS platforms provide APIs for integrating with other services and technologies.

How do I get started with using a serverless CMS for my Angular application?

Getting started with a serverless CMS for your Angular application typically involves a few steps. First, you’ll need to choose a serverless CMS that meets your needs. Next, you’ll need to set up your CMS, which may involve creating an account, configuring settings, and setting up your data structure. Then, you’ll need to integrate your CMS with your Angular application, which may involve using APIs provided by the CMS. Finally, you’ll need to test your application to ensure everything is working correctly.