GSD

How to Build a Knowledge Base Using Angular and ButterCMS

Posted by Osah Peter on October 12, 2023

A knowledge base is an online repository of information about a particular product, service, department, or topic that occasionally expands from time to time. A knowledge base's content can include tutorials, explanations, FAQs, articles, additional product information, manuals, troubleshooting guides, runbooks, and other information that visitors who access them may want or need to know.

So, it shouldn't be a surprise when we assert that every business needs a knowledge base. They provide valuable information to customers and prospects. However, the kind of data and information included in a business’s knowledge base is determined by the purpose of the business. A knowledge base may contain the following types of information:

  • Instructions and pointers for using a product or service

  • Answers to frequently asked questions (FAQ) about a product or service

  • Video demonstrations on usage of a product or service

  • Interactive guides on how to use a product or service, etc.

Advantages of having a knowledge base

Knowledge bases provide numerous advantages. A few include:

  • Increased customer retention: Customers have a do-it-yourself (DIY) mentality and would thus prefer to resolve any issues with your product before contacting customer service. A knowledge base acts as a source of truth which eventually helps them and gives them positive assurances about the product or the business.

  • Assistance to customer support officials: It is a point of reference for customers and staff to get the necessary information about a business or product.

  • Increased engagement: A knowledge base makes it easier for users to find and get the answers they need by consolidating information in a central location. In doing so, it increases the engagement of users to the company or its product as its knowledge base becomes an alternative or a go-to choice in getting answers to their questions.

Understanding Angular

Angular is a modern MVC framework and platform for developing enterprise single-page web applications (SPAs) and mobile applications. It was created in 2009 by a dedicated team at Google. Their goal was to create a free and open-source web application development framework that could be used by a community of individuals and businesses. Angular is built on TypeScript, a strict syntactical superset of JavaScript. Hence, applications built with it are more secure, error-free, and easier to debug. Angular employs a component-based approach that allows the creation of individual, self-contained pieces of code that can be reused throughout the app, thus speeding up the development process.

Angular is an ideal framework for building a knowledge base for a business or product. It possesses certain technical benefits such as

  • Reliability: It has a certain level of reliance because it is created and maintained by Google.

  • Modularity: Angular employs a modular structure which well divides the code into services and components. This is very advantageous as it gives room for improved test management and development.

  • Typescript support: it supports TypeScript, a strict syntactical superset of JavaScript. Its support for TypeScript increases its security and minimizes errors that were associated with using vanilla JavaScript.

  • Large ecosystem: It has a large community, which comes with advantages such as a low learning curve, increased resource availability, solutions to issues encountered on Github and Stack Overflow, and many more.

  • Highly functional: Angular ships with features like routing, an HTTP client, Forms, and Rx.js out of the box. This minimizes the need to install third-party packages to perform operations like routing and making HTTP requests. This makes it a more buoyant framework than other JavaScript frameworks.

A man sitting at a desk using Angular

Why you should power your knowledge base with ButterCMS

ButterCMS is a headless CMS that provides a CMS platform that consists of an intuitive dashboard and a content API. By serving as your backend content repository, ButterCMS gives complete control over the front end. It gives you the ability to manage your content on the dashboard while your content is served to different front-ends built on different technologies through its content API. Components, collections, and page types are just a few of the features in ButterCMS that make it relatively simple to create and manage content in the dashboard and ultimately serve that content to your customers wherever they may be.

In this tutorial, we will be heavily using Page types. A core feature of Butter, enabling users to configure pages once and effectively use them over and over again, ultimately, saving you a lot of time and headache.

The knowledge base will be divided into three types (that we will create using the Page Type feature):

  • The Home page type: This page type displays the content associated with the home page of the knowledge base. It also shows links to sections of the knowledge base that users can access.

  • The Section page type: This page type shows all the sections of the knowledge base. It displays links to the article or information that users can access

  • The Article page type: This page type displays the article's content.

Tutorial: Building a knowledge base with Angular and ButterCMS

In this article, we will build a clone of the ButterCMS knowledge base. The knowledge base will have all its data stored within ButterCMS.

To follow through with this article, you need the following:

  • Basic knowledge of JavaScript

  • Node JS runtime installed on your local machine

  • Angular CLI installed globally on your local machine (using NPM or Yarn)

  • An active ButterCMS account

  • Package managers like Yarn or NPM

  • A code editor (VS Code is preferred)

Tutorial objectives

Our main objective is to build a knowledge base using Angular and ButterCMS. After reading this article, we will be able to create custom page types and learn how to reference other pages. We will also learn how to use components as well as integrate ButterCMS into an Angular application.

Getting started with ButterCMS

First, sign up for ButterCMS. You can do so via email, Google sign-in, or GitHub sign-in

ButterCMS free trial account sign up form

Modeling the knowledge base page types

As stated previously, we will model our content into three page types: the Home page type, the Section page type, and the Article page type. We will start with the Article page type, then the Section page type, and then the Home page type.

Article page type

We will create the Article page type by clicking the + icon next to the Page Types option on the Content Types tab on the sidebar menu.

Select Page Types from the Content Types menu

Creating a new page type

On doing so, you will be redirected to a page where you can configure your new page.

New page type configuration page

New page type configuration page

For the Article page type, we will add the following fields:

  • article_name: This is a short text field that will bear the article name.

  • article_description: This is a long text field that will bear the article description.

  • article_body: This is a WYSIWYG editor that will bear the article's content.

Article Page Type configuration

Article_type page configuration

Next, we’re going to save this page type as article_type.

Name the new page type "article_type" and save it

Saving the article_type page type

After creating the article_type page type, we will create the section_type page. If you're creating another page type, you'll click the + icon next to the Page Types icon in the Content Types drop-down menu.

Section page type

For the Section page type, we will add the following fields:

  • section_name: This is a short text field that will bear the section name.

  • section_description: This is a long text field that will bear the article description.

  • section_article_type: This is a reference field to an article type page (a one-to-many reference—that is, this field can reference many Article page types).

Next, we’re going to save the page as our section_type page type.

Save section_type page type configuration

Saving the section_type page type

After creating the section_type page type, we will create the section_type page. If you're creating another page type you'll click the + icon next to the Page Types icon in the Content Types drop-down menu.

Home page type

For the Home page type, we will add the following fields: 

  • Knowledge-base_hero_img: This is a media field that will bear the home page image.

  • Knowledge-base_title: This is a short text field that will bear the title text of the knowledge base home page.

  • Knowlege-base_text: This is a short text field that will bear the corresponding text of the knowledge base home page.

  • Knowledge-base_subtitle: This is a short text field that will bear the subtitle text of the knowledge base home page.

  • Knowledge-base-sections: This is a reference field to a section-type page (a one-to-many reference—that is, this field can reference many section page types).

Name and save the knowledge-base-home_type page type name and configuration

Saving the knowledge-base_home_type page type

Creating the knowledge base pages

With the page types modeled in the previous section, we will now create pages using them. Let's begin by creating an article.

Creating a knowledge base article

Click the New Page button in the ButterCMS Pages section on the dashboard, and select the article_type page from the dropdown.

Select article_type from the New Page menu to create a new article_type page

Creating an article_type page from an article_type page type

On the created page, we will add the page title and API slug as page metadata information. Click the Save Page Metadata button to create a draft and proceed to the text editor.

Add new article_type page metadata

Adding metadata information

Next, we will add the content of the How to Add Images to Components article to the ButterCMS knowledge base as our first demo article.

Add content to the new article_type page

Adding the Article contents to the page

Click the Publish button to save and publish all the changes made.

Repeat the article_page creation process several times to have different articles within the section. 

Next, we will create a section page that will use the article page we created as a reference.

Creating a knowledge base section page

Click the New Page button in the ButterCMS Pages section on the dashboard, and select the section_type page from the dropdown.

Select section_type from the New Page drop down menu

Creating a section_type page 

On the created page, we will add “How To’s” as the page title and “how-tos” as the API slug for metadata information. After adding them, click the Save Page Metadata button to create a draft and proceed to the text editor.

Add section_type page metadata

Adding the section page’s metadata information

Select article for the reference field

 Adding section_article_type reference Information

In the picture above, upon clicking the Add Reference button, it brings up a popup that shows us reference article page(s) we can link to our section_article_type page type.

The added article reference

 Added section_article_type reference

In the picture above, we show the added reference article (How to Add Images to Components) to our section_article_type page type.

Add image to the page's image field

 Adding section_image

Click the Publish button to save and publish all the changes made.

Repeat the section_page creation process several times to have different sections available. 

Next, we will create a home page that will house the section pages we created as a reference.

Looking for a powerful Angular CMS that scales with your app? Get started now CTA

Creating the knowledge base Home page

Click the New Page button in the ButterCMS Pages section on the dashboard, and select the article_type page from the dropdown.

Select knowledge-base-home_type from New Page menu

Creating a knowledge-base-home_type page from the knowledge-base-home_type page type

On the created page, we will add “buttercms knowledge base home” as the page title and “buttercms-knowledge-base-home” as the API slug as page metadata information. After adding them, click the Save Page Metadata button to create a draft and proceed to the text editor.

Add page metadata

Adding knowledge-base-home_type Metadata Information

Next, we will add content from the ButterCMS knowledge base page to our created page.

Add content to the knowledge-base-home_type

Adding content to knowledge-base-home_type

Upon clicking the button Add Reference, a modal pops up. This modal displays the available section_type pages that can be referenced in the knowledge-base-sections field.

Add knowledge base section reference

 Adding knowledge-base-sections reference

The added section references

Added knowledge-base-sections reference

After adding this content, click the Publish button to save and publish all the changes made.

After adding the content, we will access the API Explorer to look at a sample of the response data from our knowledge-base-home_type page on the ButterCMS Content API. To access the API Explorer, select the API Explorer option on the menu beside the preview link.

Select API explorer

Accessing API explorer on the home page

Upon clicking on the API Explorer menu, the API Explorer pane pops up. The explorer shows us a sample of the response data from our knowledge-base-home_type page on the ButterCMS Content API. 

API explorer data

API Explorer on the home page

We have now created the data for the ButterCMS knowledge base. We will now create an Angular application that will retrieve and display the knowledge base content via the ButterCMS Content API.

Building our Angular application for the knowledge base

We will install a new Angular project with the Angular CLI. If you haven’t installed the CLI before, simply type the command below to do so:

npm install -g @angular/cli

To install our Angular application, type the commands below:

ng new buttercms-knowledge-base-app

Installing the Angular project

Installing the Angular project

Successful installation of the Angular project

Successful installation of the angular project

After installing the Angular application, we will configure Tailwind CSS for our Angular application. This is to expedite styling. To configure Tailwind for an angular project, follow the steps here.

Next, we will install the ButterCMS JavaScript client. This client enables us to make API calls to the ButterCMS Content API to retrieve the data created on our dashboard.

To install the client, enter the command below:

npm install buttercms —-save

Next, we will add the API read token credential to the environment.ts file in our Angular project, just as we will be adding that to the ButterCMS JavaScript client. You can always get your API read token from the settings menu in the ButterCMS dashboard.

Access account settings

Accessing the settings menu

Account Read API token

The Read API Token

Next, we will add the Read API Token obtained from the Butter dashboard into the environment files that Angular provides. This is to enable us to inject the token into the service. 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,
   title: 'ButterCMS dev',
   apiToken: 'type token'
};

After installing and adding the token, we will create the core, pages, and components folders inside the src folder. These folders will house the services applications we will use as well as the pages and components. Upon adding these folders, the application structure should look as follows:

BUTTERCMS-KNOWLEDGE-BASE-APP    (Root Directory)         
├─ src    
├─ app
├─ components
├─ core
├─ pages                        
├─ assets                                     
├─ environments                              
├─ favicon.ico        
├─ index.html             
├─ main.ts
├─ polyfill.ts
├─ styles.scss
├─ test.ts
├─ package.json
├─ package-lock.json      
├─ angular.json   
└─ yarn.lock
….

Project structure upon adding the pages, components, and core folders to the app folder of the project.

We will add our global styles to the src/styles.css file:

/* You can add global styles to this file, and also import other style files */
@tailwind base;
@tailwind components;
@tailwind utilities;


* {
   box-sizing: border-box;
}


body {
   margin: 0;
   font-family: "Proxima Nova", sans-serif;
   font-size: 1rem;
   font-weight: 400;
   line-height: 1.5;
   color: #384062;
   text-align: left;
   background-color: #fff;
   -webkit-font-smoothing: antialiased;
   font-smoothing: antialiased;
   text-size-adjust: none;
   font-size: 16px;
   line-height: 1.3;
   width: 100%;
}


/* typography */


h1, h2, h3, h4, .section-legal .section-legal__body dt, .section-legal .section-legal-security__body dt, h5, h6 {
   font-weight: 600;
}


h1:last-child, h2:last-child, h3:last-child, h4:last-child,
.section-legal .section-legal__body dt:last-child,
.section-legal .section-legal-security__body dt:last-child,
h5:last-child, h6:last-child, p:last-child {
   margin-bottom: 0;
}


h2 {
   font-size: 35px;
   line-height: 1.2;
   display: block;
}


h5 {
   font-size: 28px;
   line-height: 1.2;
}


h6 {
   font-size: 18px;
   font-weight: 400;
   line-height: 1.44;
}


article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, main {
   display: block;
}


ol, ul, dl {
   margin-top: 0;
   margin-bottom: 1rem;
}


svg {
   overflow: hidden;
   vertical-align: middle;
}


input[type=text], input[type=password], input[type=date], input[type=datetime], input[type=datetime-local], input[type=month], input[type=week], input[type=email], input[type=number], input[type=search], input[type=tel], input[type=time], input[type=url], input[type=color], textarea, a[href^=tel] {
   appearance: none;
}


[type=search] {
   outline-offset: -2px;
   -webkit-appearance: none;
}


button, input, optgroup, select, textarea {
   font-family: inherit;
   font-size: inherit;
   color: inherit;
   -moz-osx-font-smoothing: grayscale;
   -webkit-font-smoothing: antialiased;
   font-smoothing: antialiased;
}


button, input {
   overflow: visible;
}


input, button, select, optgroup, textarea {
   margin: 0;
   font-family: inherit;
   font-size: inherit;
   line-height: inherit;
}


/* container */
.container {
   width: 100%;
   padding-right: 15px;
   padding-left: 15px;
   margin-right: auto;
   margin-left: auto;
}


@media (min-width: 576px){
   .container {
       max-width: 1200px;
   }
}

Adding styles in the /src/styles.scss file

Now, use the ng serve command to launch the development server.

Developing the knowledge base components

The knowledge base will have a home page that displays an overview of the base along with links to sections within the base that a user can click on to navigate around. Let's start by creating the home components that will house this content.

We will first create a component. To do this, open the terminal and enter the following commands in sequence in the src/app/pages folder:

ng generate component Home

ng generate component view-knowledge-base-section

ng generate component view-knowledge-base-article

ng generate component search-knowledge-base

After entering the command, the folder structure will be as follows:

BUTTERCMS-KNOWLEDGE-BASE-APP    (Root Directory)         
├─ src    
├─ app
├─ components
├─ core
├─ pages   
       ├─ home  
       ├─ search-knowledge-base 
       ├─ view-knowledge-base-article 
       ├─ view-knowledge-base-section               
├─ assets                                     
├─ environments                              
├─ favicon.ico        
├─ index.html             
├─ main.ts
├─ polyfill.ts
├─ styles.scss
├─ test.ts
├─ package.json
├─ package-lock.json      
├─ angular.json   
└─ yarn.lock
….

Project structure upon adding the home, search-knowledge-base, view-knowledge-base-article, and view-knowledge-base-section components to the pages folder.

From the structure, we created the pages: 

  • home, which will hold the knowledge base home page

  • search-knowledge-base, which is the page that enables us to search through the knowledge base

  • view-knowledge-base-article, which shows us a single article of the knowledge base

  • view-knowledge-base-section, which shows us a single section of the knowledge base

Next, let’s create the routes for these pages. Head over to app/app-routing.module.ts and modify the file with the snippet of code below:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './pages/home/home.component';
import { SearchKnowledgeBaseComponent } from './pages/search-knowledge-base/search-knowledge-base.component';
import { ViewKnowledgeBaseArticleComponent } from './pages/view-knowledge-base-article/view-knowledge-base-article.component';
import { ViewKnowledgeBaseSectionComponent } from './pages/view-knowledge-base-section/view-knowledge-base-section.component';


const routes: Routes = [
 { path: 'knowledge-base', component: HomeComponent },
 { path: 'knowledge-base/section/:id', component: ViewKnowledgeBaseSectionComponent },
 { path: 'knowledge-base/section/:sectionId/article/:articleId', component: ViewKnowledgeBaseArticleComponent },
 { path: 'knowledge-base/search', component: SearchKnowledgeBaseComponent },
 { path: '', redirectTo: 'knowledge-base', pathMatch: 'full' },
];


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

Updating the contents of the /app/app-routing.module.ts file.

Next, head over to app/app.component.html and modify it to the following:

<div>
  <router-outlet></router-outlet>
</div>

Connecting to Butter: Creating the ButterCMS service

We will create a service. A service in Angular is an object that is instantiated only once during the lifetime of an application. It includes methods for keeping data available throughout the life of an application. We can use a service to integrate into an API and the service can be used in any component of choice by dependency injection, which Angular provides out of the box.

To do this, open the terminal and enter the following commands in sequence in the src/app/core folder.

ng generate service butter-cms

After entering the command, the folder structure will be as follows:

BUTTERCMS-KNOWLEDGE-BASE-APP    (Root Directory)         
├─ src    
├─ app
├─ components
├─ core
       ├─ butter-cms.service.ts
       ├─ butter-cms.service.spec.ts
├─ pages   
       ├─ home  
       ├─ search-knowledge-base 
       ├─ view-knowledge-base-article 
       ├─ view-knowledge-base-section               
├─ assets                                     
├─ environments                              
├─ favicon.ico        
├─ index.html             
├─ main.ts
├─ polyfill.ts
├─ styles.scss
├─ test.ts
├─ package.json
├─ package-lock.json      
├─ angular.json   
└─ yarn.lock
….

Project structure upon adding the butter-cms.service.ts service file.

Next, we will add the snippet of code to the core/butter-cms.service.ts file:

import { Injectable } from '@angular/core';
import Butter from 'buttercms';
import { environment } from 'src/environments/environment';


@Injectable({
 providedIn: 'root'
})
export class ButterCMSService {
 butter: any;
  constructor() {
   this.butter = Butter(environment.apiToken);
 }




 getKnowledgeBaseHomeType() {
   return this.butter.page.retrieve('*', 'buttercms-knowledge-base-home');
 }


 SearchKnowledgeBaseSection(page_type: string, page:number = 1, page_size: number = 10) {
   return this.butter.page.search(page_type, {
     page_type: 'article_type'
   });
 }


 getKnowledgeBaseSectionType(section: string) {
   return this.butter.page.retrieve('*', section);
 }


 getKnowledgeBaseArticleType(article: string) {
   return this.butter.page.retrieve('*', article);
 }


}

Adding methods butter-cms.service.ts service file to the core folder in the app folder of the project.

From the code above, we integrated the ButterCMS client we installed earlier and added the read token obtained from the environment file to the client. We also created methods that connect to the ButterCMS Content API to retrieve home page contents (method getKnowledgeBaseHomeType()), section page contents (method getKnowledgeBaseSectionType()), article contents (method getKnowledgeBaseArticleType()), and contents from making a search query to the content API (searchKnowledgeBaseSection()).

Creating the knowledge base home page and integration to ButterCMS

We will start by displaying the knowledge base home page. Head over to the app/pages/home/home.component.ts file and add the snippet of code below:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ButterCMSService } from 'src/app/core/services/butter-cms.service';


@Component({
 selector: 'app-home',
 templateUrl: './home.component.html',
 styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {


 homePageData: any;
 searchText: string = '';


 constructor(
   private butterCMSService: ButterCMSService,
   private router: Router,
   private activatedRoute: ActivatedRoute
 ) { }


 ngOnInit(): void {
   this.butterCMSService.getKnowledgeBaseHomeType()
   .then((resp: any) => {
     this.homePageData = resp?.data?.data;
     console.log('data', resp?.data?.data)
   })
   .catch(function(error: any) {
     console.log('error', error)
   });
 }
}

Code in the app/pages/home/home.component.ts file

In the code above, we simply import the service we created earlier and call the method getKnowledgeBaseHome(), which sends an HTTP request to the Content API to fetch the contents for the home page.

We will go to the app/pages/home/home.component.html file and paste the snippet of code below:

<div>
   <app-header [butterImg]="homePageData?.fields['knowledge-base_hero_img']"></app-header>


   <div class="intro intro--small"
       style="background-image: url(https://buttercms.com/static/v2/images/intro-bg-2.4af98db9356f.jpg);">
       <div class="container">
           <div class="intro__inner">
               <div class="intro__head">
                   <h1>
                       {{ homePageData?.fields['knowledge-base_title'] }}
                   </h1>
               </div>


               <div class="intro__content">
                   <div class="search">
                       <form>
                           <i class="ico-search">
                               <svg>
                                   <use
                                       xlink:href="https://buttercms.com/static/v2/images/sprite.1229afca4c5f.svg#ico-search">
                                   </use>
                               </svg>
                           </i>


                           <input [(ngModel)]="searchText" type="search" name="query" id="query" value="" placeholder="Search here" class="search__field"/>


                           <button (click)="handleSearch()" type="submit" class="search__btn btn btn--solid-yellow">
                               <i class="ico-arrow-send-white">
                                   <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/></svg>
                               </i>
                           </button>
                       </form>
                   </div>
               </div>
           </div>
       </div>
   </div>




   <main class="main">
       <section class="container section-services-alt">
           <div class="section-services__head">
               <h2>
                   {{ homePageData?.fields['knowlege-base_text'] }}
               </h2>


               <p>
                   {{ homePageData?.fields['knowledge-base_subtitle'] }}
               </p>
           </div>


           <div class="section-services__body">
               <div class="services-alt">
                   <div class="flex flex-wrap">


                       <div *ngFor="let data of homePageData?.fields['knowledge-base-sections']" class="w-full md:w-4/12">
                           <div class="service-alt">
                               <div class="service__head">
                                   <div class="service__image">
                                       <img [src]="data?.fields?.section_image" alt="How To's">
                                   </div>


                                   <div class="service__content">
                                       <h5>
                                           {{ data?.fields?.section_name }}
                                       </h5>
                                   </div>
                               </div>


                               <div class="service__body">
                                   <p>
                                       {{ data?.fields?.section_description }}
                                   </p>
                               </div>
                               <a [routerLink]="['/knowledge-base/section',  data?.slug]"></a>
                           </div>
                       </div>
                   </div>
               </div>
           </div>
       </section>
   </main>
</div>

Code in app/pages/home/home.component.html file

In the code above, we simply render the home page data obtained from calling the method getKnowledgeBaseHome() on the ButterCMS service.

Next, we will add styling to the component. We will go to the app/pages/home/home.component.scss file and paste the snippet of code below:

.intro {
   position: relative;
   padding: 176px 0 87px;
   background-repeat: no-repeat;
   background-size: cover;
   background-position: center center;
   color: #fff;
   text-align: center;
   width: 100%;

   &.intro--small {
       position: relative;
       padding: 193px 0 36px;
       margin: 0 0 40px;
       width: 100%;

       & .intro__inner {
           & .intro__head {
               margin: 0 0 74px;
  
               @media (max-width: 767px){
                   & {
                       margin-bottom: 48px;
                   }
               }
  
               h1 {
                   font-size: 45px;
                   line-height: 1.09;
               }
           }
       }
   }

   @media (max-width: 767px){
       .intro {
           padding: 106px 0 40px;
       }
   }

   @media (max-width: 767px){
       .intro--small {
           margin: 0 0 25px;
       }
   }

   & .intro__inner {
       max-width: 1110px;
       margin: 0 auto;
   }
}

.search {
   width: 573px;
   position: absolute;
   bottom: -35px;
   left: 50%;
   transform: translateX(-50%);
   box-shadow: 0px 22px 76px 0px rgb(0 0 0 / 16%);

   @media (max-width: 767px){
       & {
           width: 95%;
           bottom: -20px;
       }
   }

   & .btn {
       position: relative;
       left: -1px;
       flex: 0 0 73px;
       max-width: 73px;
       height: 70px;
       padding: 0;
       min-width: unset;
       line-height: 70px;
       border-radius: 0;
       border-bottom-right-radius: 2px;
       border-top-right-radius: 2px;
       transition: opacity 0.3s ease;

       @media (max-width: 767px){
           & {
               flex: 0 0 40px;
               max-width: 40px;
               height: 40px;
               line-height: 38px;
           }
       }
   }

   & form {
       display: flex;
   }

   & .search__field {
       flex: 0 0 500px;
       max-width: 500px;
       height: 70px;
       padding: 0 50px;
       border-bottom-right-radius: 2px;
       border-top-right-radius: 2px;
       border: 0;
       color: #19234F;
       outline: none;

       &::placeholder {
           color: #19234F;
       }

       @media (max-width: 767px){
           & {
               flex: 1;
               padding: 0 20px 0 50px;
               max-width: unset;
               height: 40px;
           }
       }
   }

   & .ico-search {
       position: absolute;
       pointer-events: none;
       left: 27px;
       top: 50%;
       transform: translateY(-50%);
   }

   .ico-search {
       width: 14px;
       height: 14px;
       line-height: 0;
   }
  
   [class*=ico-] {
       display: inline-block;
       vertical-align: middle;
   }

   .ico-arrow-send-white, .ico-arrow-send {
       width: 20px;
       height: 13px;
       line-height: 0;

       & svg {
           width: 100%;
           height: 100%;
       }
   }

   [class*=ico-] {
       display: inline-block;
       vertical-align: middle;
       & svg {
           width: 100%;
           height: 100%;
           fill: inherit;
       }
   }

   svg:not(:root) {
       overflow: hidden;
   }
}

.btn--solid-yellow {
   background: #FED700;
   border-color: #FED700;
}

.section-services-alt {
   padding: 85px 0 14px;

   & .section-services__head {
       margin: 0 0 93px;
       text-align: center;
   }

   & h2 {
       margin: 0 0 32px;
   }

   @media (max-width: 1650px){
       .section-services-alt {
           padding: 85px 20px 14px;
       }
   }

   & .service-alt {
       height: 100%;
       position: relative;
       display: flex;
       flex-direction: column;
       margin: 0 0 30px;
       padding: 33px 21px 35px 38px;
       border-radius: 4px;
       transition: box-shadow 0.3s ease;

       &:hover {
           box-shadow: 0px 13px 26px 0px rgb(0 0 0 / 16%);
       }

       & .service__head {
           display: flex;
           align-items: center;
           margin: 0 0 24px;

           & .service__image {
               position: relative;
               flex: 0 0 70px;
               height: 70px;
               margin: 0 10px 0 0;
               border-radius: 50%;
               background-color: #19234F;
               display: flex;
               justify-content: center;
               align-items: center;
           }

           & .service__content {
               & h5 {
                   font-size: 25px;
                   letter-spacing: 0.02em;
               }
           }
       }

       & .service__body {
           flex: 1 1 auto;
       }

       & h5 {
           font-size: 25px;
           letter-spacing: 0.02em;
       }

       & p {
           line-height: 1.88;
       }

       & > a {
           position: absolute;
           top: 0;
           left: 0;
           right: 0;
           bottom: 0;
           color: inherit;
           text-decoration: none;
           transition: opacity 0.4s ease-in-out;

           &:hover, &[href^=tel] {
               text-decoration: none;
           }
          
           &:hover {
               color: inherit;
               opacity: 0.8;
           }


           &:hover {
               color: #0056b3;
               text-decoration: underline;
           }
       }
      
   }
}

styling in the app/pages/home/home.component.scss file

After adding the styling, when we open our app in the browser, we will see the following:

Preview of KB home page

Preview of the knowledge base Homepage 

From the preview, we can see the home page contents we created in the ButterCMS dashboard.

Looking for a powerful Angular CMS that scales with your app? Get started now CTA

Creating the knowledge base section page and integration to ButterCMS

We will link content on the home page to a section so that, upon clicking them, we will be able to navigate to that section. Head over to the app/pages/view-knowledge-base-section/view-knowledge-base-section.component.ts file and paste the snippet of code below:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { ButterCMSService } from 'src/app/core/services/butter-cms.service';

@Component({
 selector: 'app-view-knowledge-base-section',
 templateUrl: './view-knowledge-base-section.component.html',
 styleUrls: ['./view-knowledge-base-section.component.scss'],
})
export class ViewKnowledgeBaseSectionComponent implements OnInit {

 id: string | any ;
 sectionData: any;

 constructor(
   private route: ActivatedRoute,
   private butterCMSService: ButterCMSService
 ) {}

 ngOnInit(): void {
   this.id = this.route.snapshot.paramMap.get('id')
   console.log('id', this.id);

   this.butterCMSService.getKnowledgeBaseSectionType(this.id)
   .then((resp: any) => {
     this.sectionData = resp?.data?.data;
     console.log('section data', resp?.data?.data)
   })
   .catch(function(error: any) {
     console.log('section error', error)
   });
 }
}

Code in the app/pages/view-knowledge-base-section/view-knowledge-base-section.component.ts file

In the code above, we simply import the service we created earlier and call the method getKnowledgeBaseSectionType() which sends an HTTP request to the Content API to fetch the content for a section using the slug (ID) of that section.

We will go to the app/pages/view-knowledge-base-section/view-knowledge-base-section.component.html file and paste the snippet of code below:

<app-header-for-sections></app-header-for-sections>


<main class="main background-gray">
   <div class="breadcrumbs container">
       <nav aria-label="breadcrumb" class="breadcrumbs-search-navbar">
           <ol class="breadcrumb">
               <li class="breadcrumb-item">
                   <a [routerLink]="'/knowledge-base/'">
                       All Categories
                   </a>
               </li>
               <li class="breadcrumb-item active" aria-current="page">
                   {{ sectionData?.fields?.section_name}}
               </li>
           </ol>

           <div class="search search--inline">
               <form>
                   <i class="ico-search">
                       <svg>
                           <use xlink:href="https://buttercms.com/static/v2/images/sprite.1229afca4c5f.svg#ico-search"></use>
                       </svg>
                   </i>

                   <input type="search" name="query" id="query" value="" placeholder="Search here"
                       class="search__field">

                   <button type="submit" class="search__btn btn btn--solid-dark-blue btn--icon">
                       <i class="ico-arrow-send-white">
                           <svg style="color: white" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" fill="white"></path> </svg>
                       </i>
                   </button>
               </form>
           </div>
       </nav>
   </div>

   <section class="section-category">
       <div class="container">
           <div class="section-category__head">
               <div class="d-inline-block">
                   <div class="category-icon">
                       <img src="https://cdn.buttercms.com/JsA9TWYQ5upTxBFMiNr9" alt="How To's">
                   </div>
               </div>
               <h1 class="d-inline-block">
                   {{ sectionData?.fields?.section_name}}
               </h1>
               <p class="mt-5">
                   {{ sectionData?.fields?.section_description }}
               </p>
           </div>

           <div class="section-category__body">
               <div class="row">
                   <div class="col">
                       <ul *ngIf="sectionData && sectionData?.fields?.section_article_type?.length > 0" class="list-group">
                           <li *ngFor="let data of sectionData?.fields?.section_article_type" class="list-group-item list-group-item-action">
                               <h5 class="d-flex justify-content-between">
                                   {{ data?.fields?.article_name }}
                               </h5>
                               <p>
                                   {{ data?.fields?.article_description }}
                               </p>
                               <a [routerLink]="['/knowledge-base/section/' + id + '/article',  data?.slug]"></a>
                           </li>
                       </ul>

                       <ul *ngIf="(!sectionData) || ( sectionData && sectionData?.fields?.section_article_type?.length === 0)" class="list-group">
                           <li class="list-group-item list-group-item-action">
                               <h5 class="d-flex justify-content-between">
                                   No Article available
                               </h5>
                           </li>
                       </ul>
                   </div>
               </div>
           </div>
       </div>
   </section>
</main>

Code in the app/pages/view-knowledge-base-section/view-knowledge-base-section.component.html file

In the code above, we simply render the section data obtained from calling the method getKnowledgeBaseSectionType() on the ButterCMS service.

Next, we will add styling to the component. We will go to the app/pages/view-knowledge-base-section/view-knowledge-base-section.component.scss file and paste the snippet of code below:

/* breadcrumbs */
.breadcrumbs {
   .breadcrumbs-search-navbar {
       background-color: #e9ecef;
       display: flex;
       align-items: center;

       .breadcrumb {
           display: flex;
           flex-wrap: wrap;
           padding: 0.75rem 1rem;
           margin-bottom: 1rem;
           list-style: none;
           background-color: #e9ecef;
           border-radius: 0.25rem;

           .breadcrumb-item {
               a {
                   color: inherit;
                   text-decoration: none;
                   transition: opacity 0.4s ease-in-out;
                   cursor: pointer;
                   background-color: transparent;
               }

               &.active {
                   color: #6c757d;
               }

               & + .breadcrumb-item {
                   padding-left: 0.5rem;
               }
           }

           .breadcrumb-item + .breadcrumb-item::before {
               display: inline-block;
               padding-right: 0.5rem;
               color: #6c757d;
               content: "/";
           }
       }
      

       & .search {
           padding: 10px;
           margin: 0 0 0 auto;

           &.search--inline {
               position: static;
               transform: translateX(0);
               max-width: 422px;
               width: 100%;
               box-shadow: none;

               & .ico-search {
                   left: 18px;
               }

               & .search__field {
                   flex: 1;
                   max-width: 349px;
                   width: 100%;
                   height: 42px;
                   padding: 0 42px;
                   appearance: none !important;

                   &::placeholder {
                       outline: none !important;
                       box-shadow: none !important;
                   }
               }

               & .btn {
                   left: -1px;
                   flex: 0 0 73px;
                   max-width: 73px;
                   min-width: 0;
                   height: 42px;
                   padding: 0;
                   line-height: 42px;
               }
           }


           & .btn {
               position: relative;
               left: -1px;
               flex: 0 0 73px;
               max-width: 73px;
               height: 70px;
               padding: 0;
               min-width: unset;
               line-height: 70px;
               border-radius: 0;
               border-bottom-right-radius: 2px;
               border-top-right-radius: 2px;
               transition: opacity 0.3s ease;


               border: 1px solid #384062;
               font-family: "Proxima Nova", sans-serif;
               font-size: 16px;
               font-weight: 600;
               vertical-align: middle;
               text-align: center;
               text-decoration: none;
               appearance: none;
               user-select: none;

               &.btn--solid-dark-blue {
                   background: #151E46;
                   border-color: #151E46;
                   color: #fff;

                   &.btn--icon svg {
                       fill: #fff;
                   }
               }
              
               &.btn--icon {
                   display: flex;
                   align-items: center;
                   justify-content: center;
                   letter-spacing: -0.01em;

                   & svg {
                       transition: fill 0.3s ease-in-out;
                   }
               }


               .ico-arrow-send-white svg, .ico-arrow-send svg {
                   width: 100%;
                   height: 100%;
               }
           }


           & .search__field {
               flex: 0 0 500px;
               max-width: 500px;
               height: 70px;
               padding: 0 50px;
               border-bottom-right-radius: 2px;
               border-top-right-radius: 2px;
               border: 0;
               color: #19234F;
               appearance: none !important;

               &::placeholder {
                   outline: none !important;
                   box-shadow: none !important;
               }
           }


           & form {
               display: flex;
           }
          
           & .ico-search {
               position: absolute;
               pointer-events: none;
               left: 27px;
               top: 50%;
               transform: translateY(-50%);
               width: 14px;
               height: 14px;
               line-height: 0;
           }
          
           [class*=ico-] {
               display: inline-block;
               vertical-align: middle;
           }


           [class*=ico-] svg {
               width: 100%;
               height: 100%;
               fill: inherit;
           }


           svg:not(:root) {
               overflow: hidden;
           }


           svg {
               overflow: hidden;
               vertical-align: middle;
           }
       }
   }


   & ol.breadcrumb {
       margin-bottom: 0;
   }
}


.background-gray {
   background-color: #f8f9fa !important;
}


/* layout */


.row {
   display: flex;
   flex-wrap: wrap;
   margin-right: -15px;
   margin-left: -15px;
}


.col {
   max-width: 100%;
   flex: 1;
}


.d-inline-block {
   display: inline-block !important;
}


.justify-content-between {
   justify-content: space-between !important;
}


.d-flex {
   display: flex !important;
}


/* section-category */
.section-category {
   padding: 20px 0 40px;
   background-color: #f8f9fa;


   & .container {
       background-color: #fff;
       padding: 3rem 2rem;
   }


   .section-category__head {
       margin-bottom: 60px;


       h1 {
           font-size: 45px;
           line-height: 1.09;
       }


       & p:last-child {
           margin-bottom: 0;
       }


       & .category-icon {
           position: relative;
           flex: 0 0 70px;
           height: 70px;
           width: 70px;
           margin: 0 10px 0 0;
           border-radius: 50%;
           background-color: #19234F;
           display: flex;
           justify-content: center;
           align-items: center;


           img {
               display: inline-block;
               max-width: 100%;
               height: auto;
               vertical-align: middle;
           }
       }
   }


   & .section-category__body {
       .row {
           display: flex;
           flex-wrap: wrap;
           margin-right: -15px;
           margin-left: -15px;


           .col {
               max-width: 100%;
               flex: 1;
           }
       }


       & .list-group-item {
           padding: 2rem 1.25rem !important;


           &:first-child {
               border-top-left-radius: 0.25rem;
               border-top-right-radius: 0.25rem;
           }


           & {
               position: relative;
               display: block;
               padding: 0.75rem 1.25rem;
               margin-bottom: -1px;
               background-color: #fff;
               border: 1px solid rgba(0, 0, 0, 0.125);
           }


           &.list-group-item-action {
               width: 100%;
               color: #495057;
               text-align: inherit;


               &:hover, &:focus {
                   z-index: 1;
                   color: #495057;
                   text-decoration: none;
                   background-color: #f8f9fa;
               }
           }


           [class^=list-] {
               list-style: none outside none;
           }


           & p {
               margin-top: 0;
               margin-bottom: 1rem;
           }


           & a {
               position: absolute;
               top: 0;
               left: 0;
               right: 0;
               bottom: 0;
               color: inherit;
               text-decoration: none;
               transition: opacity 0.4s ease-in-out;
           }


           h5 {
               font-weight: 600;
               font-size: 28px;
               line-height: 1.2;
               margin-bottom: 0.5rem;
           }
       }
   }
}


/* Articles */
.section-articles {
   padding: 120px 0 0px;
  
   @media (max-width: 1650px){
       .section-articles {
           padding: 40px 20px 0px;
       }
   }


   &.section-articles--alt {
       padding: 40px 0 127px;
   }


   & .section-articles__head {
       margin: 0 0 76px;
       text-align: center;
   }

   & .section-articles__foot {
       padding: 5px 0 0;

       & .articles-small {
           & .articles-small__title {
               margin: 0 0 40px;
               font-size: 20px;
               font-weight: 600;
           }

           & h5 {
               display: block;
               font-size: 0.83em;
               margin-block-start: 1.67em;
               margin-block-end: 1.67em;
               margin-inline-start: 0px;
               margin-inline-end: 0px;
               font-weight: bold;
           }

           & .articles-small__title {
               margin: 0 0 40px;
               font-size: 20px;
               font-weight: 600;
           }
       }
   }


   [class^=list-] {
       list-style: none outside none;
   }


   .list-group {
       display: flex;
       flex-direction: column;
       padding-left: 0;
       margin-bottom: 0;

       &:first-child {
           border-top-left-radius: 0.25rem;
           border-top-right-radius: 0.25rem;
       }

       & .list-group-item {
           position: relative;
           display: block;
           padding: 0.75rem 1.25rem;
           margin-bottom: -1px;
           background-color: #fff;
           border: 1px solid rgba(0, 0, 0, 0.125);

           & a {
               color: inherit;
               text-decoration: none;
               transition: opacity 0.4s ease-in-out;

               h6 {
                   font-size: 18px;
                   line-height: 1.44;
                   margin-bottom: 0.5rem;
                   font-weight: 600;
               }
           }

           &.list-group-item-action {
               width: 100%;
               color: #495057;
               text-align: inherit;

               &:hover, &:focus {
                   z-index: 1;
                   color: #495057;
                   text-decoration: none;
                   background-color: #f8f9fa;
               }
           }


           &:first-child {
               border-top-left-radius: 0.25rem;
               border-top-right-radius: 0.25rem;
           }
       }
   }
  

Styling in the app/pages/view-knowledge-base-section/view-knowledge-base-section.component.scss file

After adding the styling, when we open our app in the browser, we will see the following:

Knowledge Base section page

Preview of a knowledge base section 

From the preview, we can see the section content we created in the ButterCMS dashboard and the link to the article page.

Creating the knowledge base article page and integration to ButterCMS

We will link the content of the section to an article so that upon clicking the link, we will be able to view the article. Head over to the app/pages/view-knowledge-article-section/view-knowledge-base-article.component.scss file and paste the snippet of code below:

import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ButterCMSService } from 'src/app/core/services/butter-cms.service';

@Component({
 selector: 'app-view-knowledge-base-article',
 templateUrl: './view-knowledge-base-article.component.html',
 styleUrls: ['./view-knowledge-base-article.component.scss'],
 encapsulation: ViewEncapsulation.None,
})
export class ViewKnowledgeBaseArticleComponent implements OnInit {

 id: string | any ;
 articleData: any;
 sectionId: string | any;

 constructor(
   private route: ActivatedRoute,
   private butterCMSService: ButterCMSService
 ) {}

 ngOnInit(): void {
   this.sectionId = this.route.snapshot.paramMap.get('sectionId');
   this.id = this.route.snapshot.paramMap.get('articleId');
   console.log('id', this.id);
  
   this.butterCMSService.getKnowledgeBaseArticleType(this.id)
   .then((resp: any) => {
     this.articleData = resp?.data?.data;
     console.log('article data', resp?.data?.data)
   })
   .catch(function(error: any) {
     console.log('article error', error)
   });
 }
}

}

Code in the app/pages/view-knowledge-base-article/view-knowledge-base-article.component.ts file

In the code above, we simply import the service we created earlier and call the method getKnowledgeBaseArticleType() which sends an HTTP request to the Content API to fetch the contents of an article using the article slug (ID) of that section.

We will go to the app/pages/view-knowledge-base-article/view-knowledge-base-article.component.html file and paste the snippet of code below:

<app-header-for-sections></app-header-for-sections>


<main class="main background-gray">
   <div class="breadcrumbs container">
       <nav aria-label="breadcrumb" class="breadcrumbs-search-navbar">
           <ol class="breadcrumb">
               <li class="breadcrumb-item">
                   <a [routerLink]="'/knowledge-base/'">
                       All Categories
                   </a>
               </li>
               <li class="breadcrumb-item">
                   <a [routerLink]="'/knowledge-base/section/' + sectionId">
                       {{ sectionId }}
                   </a>
               </li>


               <li class="breadcrumb-item active" aria-current="page">
                   {{ articleData?.slug }}
               </li>
           </ol>


           <div class="search search--inline">
               <form>
                   <i class="ico-search">
                       <svg>
                           <use xlink:href="https://buttercms.com/static/v2/images/sprite.1229afca4c5f.svg#ico-search">
                           </use>
                       </svg>
                   </i>


                   <input type="search" name="query" id="query" value="" placeholder="Search here"
                       class="search__field">


                   <button type="submit" class="search__btn btn btn--solid-dark-blue btn--icon">
                       <i class="ico-arrow-send-white">
                           <svg style="color: white" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" fill="white"></path> </svg>
                       </i>
                   </button>
               </form>
           </div>
       </nav>
   </div>


   <section class="section-category">
       <div class="container">
           <div class="section-category__body">
               <section class="section-article">
                   <div class="container" [innerHTML]="articleData?.fields?.article_body"></div>
               </section>
           </div>
       </div>
   </section>
</main>

Code in the app/pages/view-knowledge-base-article/view-knowledge-base-article.component.html file

In the code above, we simply render the article data obtained from calling the method getKnowledgeBaseArticleType() on the ButterCMS service.

Next, we will add styling to the component. We will go to the app/pages/view-knowledge-base-article/view-knowledge-base-article.component.scss file and paste the snippet of code below:

/* breadcrumbs */
.breadcrumbs {
   .breadcrumbs-search-navbar {
       background-color: #e9ecef;
       display: flex;
       align-items: center;


       .breadcrumb {
           display: flex;
           flex-wrap: wrap;
           padding: 0.75rem 1rem;
           margin-bottom: 1rem;
           list-style: none;
           background-color: #e9ecef;
           border-radius: 0.25rem;


           .breadcrumb-item {
               a {
                   color: inherit;
                   text-decoration: none;
                   transition: opacity 0.4s ease-in-out;
                   cursor: pointer;
                   background-color: transparent;
               }


               &.active {
                   color: #6c757d;
               }


               & + .breadcrumb-item {
                   padding-left: 0.5rem;
               }
           }


           .breadcrumb-item + .breadcrumb-item::before {
               display: inline-block;
               padding-right: 0.5rem;
               color: #6c757d;
               content: "/";
           }
       }
      


       & .search {
           padding: 10px;
           margin: 0 0 0 auto;


           &.search--inline {
               position: static;
               transform: translateX(0);
               max-width: 422px;
               width: 100%;
               box-shadow: none;


               & .ico-search {
                   left: 18px;
               }


               & .search__field {
                   flex: 1;
                   max-width: 349px;
                   width: 100%;
                   height: 42px;
                   padding: 0 42px;
                   appearance: none !important;


                   &::placeholder {
                       outline: none !important;
                       box-shadow: none !important;
                   }
               }


               & .btn {
                   left: -1px;
                   flex: 0 0 73px;
                   max-width: 73px;
                   min-width: 0;
                   height: 42px;
                   padding: 0;
                   line-height: 42px;
               }
           }


           & .btn {
               position: relative;
               left: -1px;
               flex: 0 0 73px;
               max-width: 73px;
               height: 70px;
               padding: 0;
               min-width: unset;
               line-height: 70px;
               border-radius: 0;
               border-bottom-right-radius: 2px;
               border-top-right-radius: 2px;
               transition: opacity 0.3s ease;


               border: 1px solid #384062;
               font-family: "Proxima Nova", sans-serif;
               font-size: 16px;
               font-weight: 600;
               vertical-align: middle;
               text-align: center;
               text-decoration: none;
               appearance: none;
               user-select: none;




               &.btn--solid-dark-blue {
                   background: #151E46;
                   border-color: #151E46;
                   color: #fff;


                   &.btn--icon svg {
                       fill: #fff;
                   }
               }
              
               &.btn--icon {
                   display: flex;
                   align-items: center;
                   justify-content: center;
                   letter-spacing: -0.01em;


                   & svg {
                       transition: fill 0.3s ease-in-out;
                   }
               }


               .ico-arrow-send-white svg, .ico-arrow-send svg {
                   width: 100%;
                   height: 100%;
               }
           }


           & .search__field {
               flex: 0 0 500px;
               max-width: 500px;
               height: 70px;
               padding: 0 50px;
               border-bottom-right-radius: 2px;
               border-top-right-radius: 2px;
               border: 0;
               color: #19234F;
               appearance: none !important;


               &::placeholder {
                   outline: none !important;
                   box-shadow: none !important;
               }
           }


           & form {
               display: flex;
           }
          
           & .ico-search {
               position: absolute;
               pointer-events: none;
               left: 27px;
               top: 50%;
               transform: translateY(-50%);
               width: 14px;
               height: 14px;
               line-height: 0;
           }
          
           [class*=ico-] {
               display: inline-block;
               vertical-align: middle;
           }


           [class*=ico-] svg {
               width: 100%;
               height: 100%;
               fill: inherit;
           }


           svg:not(:root) {
               overflow: hidden;
           }


           svg {
               overflow: hidden;
               vertical-align: middle;
           }
       }
   }


   & ol.breadcrumb {
       margin-bottom: 0;
   }
}




.background-gray {
   background-color: #f8f9fa !important;
}


/* layout */


.row {
   display: flex;
   flex-wrap: wrap;
   margin-right: -15px;
   margin-left: -15px;
}


.col {
   max-width: 100%;
   flex: 1;
}


.d-inline-block {
   display: inline-block !important;
}


.justify-content-between {
   justify-content: space-between !important;
}


.d-flex {
   display: flex !important;
}


/* section-category */
.section-category {
   padding: 20px 0 40px;
   background-color: #f8f9fa;


   & .container {
       background-color: #fff;
       padding: 3rem 2rem;
   }


   .section-category__head {
       margin-bottom: 60px;


       h1 {
           font-size: 45px;
           line-height: 1.09;
       }


       & p:last-child {
           margin-bottom: 0;
       }


       & .category-icon {
           position: relative;
           flex: 0 0 70px;
           height: 70px;
           width: 70px;
           margin: 0 10px 0 0;
           border-radius: 50%;
           background-color: #19234F;
           display: flex;
           justify-content: center;
           align-items: center;


           img {
               display: inline-block;
               max-width: 100%;
               height: auto;
               vertical-align: middle;
           }
       }
   }


   & .section-category__body {


       h1 {
           font-weight: 600 !important;
           font-size: 45px !important;
           line-height: 1.09 !important;


           &:last-child {
               margin-bottom: 0;
           }
       }


       p {
           padding-bottom:  1rem;
       }


       .row {
           display: flex;
           flex-wrap: wrap;
           margin-right: -15px;
           margin-left: -15px;


           .col {
               max-width: 100%;
               flex: 1;
           }
       }


       & .list-group-item {
           padding: 2rem 1.25rem !important;


           &:first-child {
               border-top-left-radius: 0.25rem;
               border-top-right-radius: 0.25rem;
           }


           & {
               position: relative;
               display: block;
               padding: 0.75rem 1.25rem;
               margin-bottom: -1px;
               background-color: #fff;
               border: 1px solid rgba(0, 0, 0, 0.125);
           }


           &.list-group-item-action {
               width: 100%;
               color: #495057;
               text-align: inherit;


               &:hover, &:focus {
                   z-index: 1;
                   color: #495057;
                   text-decoration: none;
                   background-color: #f8f9fa;
               }
           }


           [class^=list-] {
               list-style: none outside none;
           }


           & p {
               margin-top: 0;
               margin-bottom: 1rem;
           }


           & a {
               position: absolute;
               top: 0;
               left: 0;
               right: 0;
               bottom: 0;
               color: inherit;
               text-decoration: none;
               transition: opacity 0.4s ease-in-out;
           }


           h5 {
               font-weight: 600;
               font-size: 28px;
               line-height: 1.2;
               margin-bottom: 0.5rem;
           }
       }


       /* article */
       .section-article {
           background-color: #f8f9fa;


           & > .container > h1 {
               font-weight: 600 !important;
               font-size: 45px !important;
               line-height: 1.09 !important;


               &:last-child {
                   margin-bottom: 0;
               }
           }


           p {
               padding-bottom:  1rem;
           }


           & .container {
               padding: 3rem 2rem;
               background-color: #fff; 
           }
           .section-article__body {}
       }
   }
}

Styling in the app/pages/view-knowledge-base-section/view-knowledge-base-section.component.scss file

After adding the styling, when we open our app in the browser, we will see the following:

Preview of KB article page

Preview of a knowledge base article

From the preview, we can see the article content we created in the ButterCMS dashboard.

Looking for a powerful Angular CMS that scales with your app? Get started now CTA

Implement article search for the knowledge base

For the home page, we will be using the ngModel directive for two-way binding for the search input. So, we will need to import the forms module to our application. To do this, head over to src/app/app.module.ts and add the following imports:

import { NgModule } from '@angular/core';

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:

searchText: string = '';

Next, let’s update the search bar in the app/pages/home/home.component.html file by adding an onclick event function handleSearch() and an ngModel to the already existing search input.

<div class="intro intro--small"
       style="background-image: url(https://buttercms.com/static/v2/images/intro-bg-2.4af98db9356f.jpg);">
       <div class="container">
           <div class="intro__inner">
               <div class="intro__head">
                   <h1>
                       {{ homePageData?.fields['knowledge-base_title'] }}
                   </h1>
               </div>

               <div class="intro__content">
                   <div class="search">
                       <form>
                           <i class="ico-search">
                               <svg>
                                   <use
                                       xlink:href="https://buttercms.com/static/v2/images/sprite.1229afca4c5f.svg#ico-search">
                                   </use>
                               </svg>
                           </i>

                           <input [(ngModel)]="searchText" type="search" name="query" id="query" value="" placeholder="Search here" class="search__field"/>

                           <button (click)="handleSearch()" type="submit" class="search__btn btn btn--solid-yellow">
                               <i class="ico-arrow-send-white">
                                   <svg style="color: white" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" fill="white"></path> </svg>
                               </i>
                           </button>
                       </form>
                   </div>
               </div>
           </div>
       </div>
   </div>

A section of code in the app/pages/home/home.component.ts file

After adding the HTML code, head over to the app/pages/home/home.component.ts file and update the component with the snippet of code below:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ButterCMSService } from 'src/app/core/services/butter-cms.service';


@Component({
 selector: 'app-home',
 templateUrl: './home.component.html',
 styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {


 homePageData: any;
 searchText: string = '';


 constructor(
   private butterCMSService: ButterCMSService,
   private router: Router,
   private activatedRoute: ActivatedRoute
 ) { }


 ngOnInit(): void {
   this.butterCMSService.getKnowledgeBaseHomeType()
   .then((resp: any) => {
     this.homePageData = resp?.data?.data;
     console.log('data', resp?.data?.data)
   })
   .catch(function(error: any) {
     console.log('error', error)
   });
 }


 handleSearch() {
   this.router.navigate(['search'], {
     relativeTo: this.activatedRoute,
     queryParams: {
       query: this.searchText
     },
     queryParamsHandling: 'merge',
     skipLocationChange: true
   });
 }
}

In the code above, the ngModel stores the search text added to the search input and the handleSearch() function handles sending the search query to the search results page.

Next, we will fill the search results page with code. Head over to app/pages/search-knowledge-base/search-knowledge-base.component.ts file and add the snippet of code below:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ButterCMSService } from 'src/app/core/services/butter-cms.service';


@Component({
 selector: 'app-search-knowledge-base',
 templateUrl: './search-knowledge-base.component.html',
 styleUrls: ['./search-knowledge-base.component.scss']
})
export class SearchKnowledgeBaseComponent implements OnInit {
 id: string | any ;
 knowledgeBaseData: any;


 constructor(
   private route: ActivatedRoute,
   private butterCMSService: ButterCMSService
 ) {}


 ngOnInit(): void {
   this.route.queryParams
     .subscribe(params => {
       this.id = params['query'];
       console.log(params);
     }
   );


   this.butterCMSService.SearchKnowledgeBaseSection(this.id)
   .then((resp: any) => {
     this.knowledgeBaseData = resp?.data?.data;
     console.log('section data', resp?.data?.data)
   })
   .catch(function(error: any) {
     console.log('section error', error)
   });
 }
}

Code in the app/pages/search-knowledge-base/search-knowledge-base.component.ts file

In the code above, we simply import the service we created earlier and call the method searchKnowledgeBase() which sends an HTTP request to the Content API to search for articles using a search keyword.

Next, we will go to the app/pages/search-knowledge-base/search-knowledge-base.component.html file and paste the snippet of code below:

<app-header-for-sections></app-header-for-sections>


<main class="main background-gray">
   <div class="breadcrumbs container">
       <nav aria-label="breadcrumb" class="breadcrumbs-search-navbar">
           <ol class="breadcrumb">
               <li class="breadcrumb-item">
                   <a [routerLink]="'/knowledge-base/'">
                      Search
                   </a>
               </li>
               <li class="breadcrumb-item active" aria-current="page">
                   {{ id }}
               </li>
           </ol>


           <div class="search search--inline">
               <form>
                   <i class="ico-search">
                       <svg>
                           <use xlink:href="https://buttercms.com/static/v2/images/sprite.1229afca4c5f.svg#ico-search"></use>
                       </svg>
                   </i>


                   <input type="search" name="query" id="query" value="" placeholder="Search here"
                       class="search__field">


                   <button type="submit" class="search__btn btn btn--solid-dark-blue btn--icon">
                       <i class="ico-arrow-send-white">
                           <svg>
                               <use xlink:href="https://buttercms.com/static/v2/images/sprite.1229afca4c5f.svg#ico-arrow-send-white"></use>
                           </svg>
                       </i>
                   </button>
               </form>
           </div>
       </nav>
   </div>


   <section class="section-category">
       <div class="container">
           <div class="section-category__head">
               <div class="d-inline-block">
                   <div class="category-icon">
                       <img src="https://cdn.buttercms.com/JsA9TWYQ5upTxBFMiNr9" alt="How To's">
                   </div>
               </div>
               <h1 class="d-inline-block">
                   Search for {{ id }}
               </h1>
               <p class="mt-5">
                   {{ knowledgeBaseData?.fields?.section_description }}
               </p>
           </div>


           <div *ngIf="knowledgeBaseData && knowledgeBaseData?.length > 0" class="section-category__body">
               <div class="row">
                   <div class="col">
                       <ul  class="list-group">
                           <li *ngFor="let data of knowledgeBaseData" class="list-group-item list-group-item-action">
                               <h5 class="d-flex justify-content-between">
                                   {{ data?.fields?.article_name }}
                               </h5>
                               <p>
                                   {{ data?.fields?.article_description }}
                               </p>
                               <a [routerLink]="['/knowledge-base/section/' + id + '/article',  data?.slug]"></a>
                           </li>
                       </ul>
                   </div>
               </div>
           </div>


           <div *ngIf="(!knowledgeBaseData) || ( knowledgeBaseData && knowledgeBaseData?.length === 0)" class="section-category__body">
               <div class="row">
                 <div class="col">
                   <p>
                     No results found. Try a different search query.
                   </p>
                 </div>
               </div>
           </div>
       </div>
   </section>
</main>

Code in the app/pages/search-knowledge-base/search-knowledge-base.component.html file

In the code above, we simply render the search results obtained from calling the method searchKnowledgeBase() on the ButterCMS service.

Next, we will add styling to the component. We will go to the app/pages/search-knowledge-base/search-knowledge-base.component.scss file and paste the snippet of code below:

/* breadcrumbs */
.breadcrumbs {
   .breadcrumbs-search-navbar {
       background-color: #e9ecef;
       display: flex;
       align-items: center;

       .breadcrumb {
           display: flex;
           flex-wrap: wrap;
           padding: 0.75rem 1rem;
           margin-bottom: 1rem;
           list-style: none;
           background-color: #e9ecef;
           border-radius: 0.25rem;

           .breadcrumb-item {
               a {
                   color: inherit;
                   text-decoration: none;
                   transition: opacity 0.4s ease-in-out;
                   cursor: pointer;
                   background-color: transparent;
               }

               &.active {
                   color: #6c757d;
               }

               & + .breadcrumb-item {
                   padding-left: 0.5rem;
               }
           }

           .breadcrumb-item + .breadcrumb-item::before {
               display: inline-block;
               padding-right: 0.5rem;
               color: #6c757d;
               content: "/";
           }
       }
  
       & .search {
           padding: 10px;
           margin: 0 0 0 auto;

           &.search--inline {
               position: static;
               transform: translateX(0);
               max-width: 422px;
               width: 100%;
               box-shadow: none;

               & .ico-search {
                   left: 18px;
               }

               & .search__field {
                   flex: 1;
                   max-width: 349px;
                   width: 100%;
                   height: 42px;
                   padding: 0 42px;
                   appearance: none !important;

                   &::placeholder {
                       outline: none !important;
                       box-shadow: none !important;
                   }
               }

               & .btn {
                   left: -1px;
                   flex: 0 0 73px;
                   max-width: 73px;
                   min-width: 0;
                   height: 42px;
                   padding: 0;
                   line-height: 42px;
               }
           }

           & .btn {
               position: relative;
               left: -1px;
               flex: 0 0 73px;
               max-width: 73px;
               height: 70px;
               padding: 0;
               min-width: unset;
               line-height: 70px;
               border-radius: 0;
               border-bottom-right-radius: 2px;
               border-top-right-radius: 2px;
               transition: opacity 0.3s ease;

               border: 1px solid #384062;
               font-family: "Proxima Nova", sans-serif;
               font-size: 16px;
               font-weight: 600;
               vertical-align: middle;
               text-align: center;
               text-decoration: none;
               appearance: none;
               user-select: none;

               &.btn--solid-dark-blue {
                   background: #151E46;
                   border-color: #151E46;
                   color: #fff;

                   &.btn--icon svg {
                       fill: #fff;
                   }
               }
              
               &.btn--icon {
                   display: flex;
                   align-items: center;
                   justify-content: center;
                   letter-spacing: -0.01em;

                   & svg {
                       transition: fill 0.3s ease-in-out;
                   }
               }

               .ico-arrow-send-white svg, .ico-arrow-send svg {
                   width: 100%;
                   height: 100%;
               }
           }

           & .search__field {
               flex: 0 0 500px;
               max-width: 500px;
               height: 70px;
               padding: 0 50px;
               border-bottom-right-radius: 2px;
               border-top-right-radius: 2px;
               border: 0;
               color: #19234F;
               appearance: none !important;

               &::placeholder {
                   outline: none !important;
                   box-shadow: none !important;
               }
           }

           & form {
               display: flex;
           }
          
           & .ico-search {
               position: absolute;
               pointer-events: none;
               left: 27px;
               top: 50%;
               transform: translateY(-50%);
               width: 14px;
               height: 14px;
               line-height: 0;
           }
          
           [class*=ico-] {
               display: inline-block;
               vertical-align: middle;
           }

           [class*=ico-] svg {
               width: 100%;
               height: 100%;
               fill: inherit;
           }

           svg:not(:root) {
               overflow: hidden;
           }

           svg {
               overflow: hidden;
               vertical-align: middle;
           }
       }
   }

   & ol.breadcrumb {
       margin-bottom: 0;
   }
}

.background-gray {
   background-color: #f8f9fa !important;
}

/* layout */

.row {
   display: flex;
   flex-wrap: wrap;
   margin-right: -15px;
   margin-left: -15px;
}

.col {
   max-width: 100%;
   flex: 1;
   flex-basis: 0;
   flex-grow: 1;
   position: relative;
   width: 100%;
   padding-right: 15px;
   padding-left: 15px;
}

.d-inline-block {
   display: inline-block !important;
}

.justify-content-between {
   justify-content: space-between !important;
}

.d-flex {
   display: flex !important;
}


/* section-category */
.section-category {
   padding: 20px 0 40px;
   background-color: #f8f9fa;

   & .container {
       background-color: #fff;
       padding: 3rem 2rem;
   }

   .section-category__head {
       margin-bottom: 60px;

       h1 {
           font-size: 45px;
           line-height: 1.09;
       }

       & p:last-child {
           margin-bottom: 0;
       }

       & .category-icon {
           position: relative;
           flex: 0 0 70px;
           height: 70px;
           width: 70px;
           margin: 0 10px 0 0;
           border-radius: 50%;
           background-color: #19234F;
           display: flex;
           justify-content: center;
           align-items: center;

           img {
               display: inline-block;
               max-width: 100%;
               height: auto;
               vertical-align: middle;
           }
       }
   }

   & .section-category__body {
       .row {
           display: flex;
           flex-wrap: wrap;
           margin-right: -15px;
           margin-left: -15px;

           .col {
               max-width: 100%;
               flex: 1;
           }
       }

       & .list-group-item {
           padding: 2rem 1.25rem !important;

           &:first-child {
               border-top-left-radius: 0.25rem;
               border-top-right-radius: 0.25rem;
           }

           & {
               position: relative;
               display: block;
               padding: 0.75rem 1.25rem;
               margin-bottom: -1px;
               background-color: #fff;
               border: 1px solid rgba(0, 0, 0, 0.125);
           }

           &.list-group-item-action {
               width: 100%;
               color: #495057;
               text-align: inherit;

               &:hover, &:focus {
                   z-index: 1;
                   color: #495057;
                   text-decoration: none;
                   background-color: #f8f9fa;
               }
           }

           [class^=list-] {
               list-style: none outside none;
           }

           & p {
               margin-top: 0;
               margin-bottom: 1rem;
           }

           & a {
               position: absolute;
               top: 0;
               left: 0;
               right: 0;
               bottom: 0;
               color: inherit;
               text-decoration: none;
               transition: opacity 0.4s ease-in-out;
           }

           h5 {
               font-weight: 600;
               font-size: 28px;
               line-height: 1.2;
               margin-bottom: 0.5rem;
           }
       }
   }
}

/* Articles */
.section-articles {
   padding: 120px 0 0px;
  
   @media (max-width: 1650px){
       .section-articles {
           padding: 40px 20px 0px;
       }
   }


   &.section-articles--alt {
       padding: 40px 0 127px;
   }


   & .section-articles__head {
       margin: 0 0 76px;
       text-align: center;
   }

   & .section-articles__foot {
       padding: 5px 0 0;

       & .articles-small {
           & .articles-small__title {
               margin: 0 0 40px;
               font-size: 20px;
               font-weight: 600;
           }

           & h5 {
               display: block;
               font-size: 0.83em;
               margin-block-start: 1.67em;
               margin-block-end: 1.67em;
               margin-inline-start: 0px;
               margin-inline-end: 0px;
               font-weight: bold;
           }

           & .articles-small__title {
               margin: 0 0 40px;
               font-size: 20px;
               font-weight: 600;
           }
       }
   }

   [class^=list-] {
       list-style: none outside none;
   }

   .list-group {
       display: flex;
       flex-direction: column;
       padding-left: 0;
       margin-bottom: 0;

       &:first-child {
           border-top-left-radius: 0.25rem;
           border-top-right-radius: 0.25rem;
       }

       & .list-group-item {
           position: relative;
           display: block;
           padding: 0.75rem 1.25rem;
           margin-bottom: -1px;
           background-color: #fff;
           border: 1px solid rgba(0, 0, 0, 0.125);

           & a {
               color: inherit;
               text-decoration: none;
               transition: opacity 0.4s ease-in-out;

               h6 {
                   font-size: 18px;
                   line-height: 1.44;
                   margin-bottom: 0.5rem;
                   font-weight: 600;
               }
           }

           &.list-group-item-action {
               width: 100%;
               color: #495057;
               text-align: inherit;

               &:hover, &:focus {
                   z-index: 1;
                   color: #495057;
                   text-decoration: none;
                   background-color: #f8f9fa;
               }
           }

           &:first-child {
               border-top-left-radius: 0.25rem;
               border-top-right-radius: 0.25rem;
           }
       }
   }
}

Styling in the app/pages/search-knowledge-base/search-knowledge-base.component.scss file

With this, when we open our app in the browser, we will see the following:

Preview of the search function

A preview of the search feature 

Our rendered knowledge base

When we combine all of the components we've created, we get a well-designed knowledge base, as shown below.

Final KB home page - top half

A preview of the home page of the knowledge base

Final KB home page - bottom half

A preview of the sections on the home page of the knowledge base

Rendered How To section page

A preview of the link to an article page in a section of the knowledge base

Rendered KB article - top half

A preview of an article in the knowledge base

Rendered KB article - bottom half

A preview of a part of the article page in the knowledge base

Final thoughts

In this Angular app tutorial, we learned how to use ButterCMS to build a knowledge base in an Angular application, as well as how to create page types, pages from a page type, and reference pages in other pages. With the knowledge obtained from this tutorial, you can easily set up a knowledge base as well as create an array of diverse applications with ButterCMS using its page type feature. To further explore how to use components to build a landing page with ButterCMS, you can visit here.

If you'd like to learn more about how you can use Angular with ButterCMS, we encourage you to checkout the tutorials below:

Resources

The code for this tutorial can be found in this GitHub repo.

Make sure you receive the freshest Angular tutorials and Butter product updates.
    
Osah Peter

Osah Peter is a Software Engineer who is fascinated by web technologies. He is passionate about learning and acquiring knowledge and is keen on developing ways to pass knowledge to others in a simple manner.

ButterCMS is the #1 rated Headless CMS

G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award

Don’t miss a single post

Get our latest articles, stay updated!