This project was created as an example use case of ButterCMS and Angular, and will not be actively maintained. It has been archived, but if you’re interested in exploring the best, most up-to-date way to integrate Butter into Angular, you can check out the following resources:
The following turn-key starters are fully integrated with dynamic sample content from your ButterCMS account, including main menu, pages, blog posts, categories, and tags, all with a beautiful, custom theme with already-implemented search functionality. All of the included sample content is automatically created in your account dashboard when you sign up for a free trial of ButterCMS.
- Angular Starter
- React Starter
- Vue.js Starter
- Or see a list of all our currently-maintained starters. (Over a dozen and counting!)
- Check out the official ButterCMS Docs
- Check out the official ButterCMS API docs
Install Angular cli
npm install -g @angular/cli
ng version would result in :
Package Version
------------------------------------------------------
@angular-devkit/architect 0.900.4
@angular-devkit/core 9.0.5
@angular-devkit/schematics 9.0.5
@schematics/angular 9.0.5
@schematics/update 0.900.4
rxjs 6.5.4
typescript 3.6.5
Setup a new Angular project using Angular cli
ng new buttercms-angular-nine
In angular cli prompt, say YES
to Angular Routing and choose SCSS
using the arrow keys for the stylesheet format
change directory to the newly created angular application
cd buttercms-angular-nine
Install Angular Material and Angular Material related packages
npm install --save @angular/material @angular/cdk
npm install -S @angular/flex-layout
Install ButterCMS
npm i -S buttercms
The source code is available on Github
Open the project in your code editor of choice.
Under src/app create a directory called services
We create a file called butterCMS.service.ts
. This allows us to have our API Token in one place and not accidentally alter it.
import Butter from 'buttercms';
export const butterService = Butter('your_api_token');
We'll import this file into any component we want to use ButterCMS.
For a Quickstart create a component in angular to load the sample headlines and posts from buttercms
from your buttercms-angular-nine
directory type:
ng g c hello-you -s true -t true --skipTests
This will create a component called hello-you
with inline-style and inline-template with no spec file.
angular cli will also include this component in your app.module.ts
import {butterService} from '../services';
Inside HelloYouComponent create methods:
private fetchPosts() {
butterService.post.list({
page: 1,
page_size: 10
})
.then((res) => {
console.log('Content from ButterCMS');
console.log(res);
this.posts = res.data;
});
}
Now call this method when the component is loaded by adding it to the OnInit
lifecycle hook:
ngOnInit() {
this.fetchPosts();
}
This API request fetches our 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:
private fetchHeadline() {
butterService.content.retrieve(['homepage_headline'])
.then((res) => {
console.log('Headline from ButterCMS');
console.log(res);
this.headlines = res.data;
});
}
Add this method to the OnInit
lifecycle hook.
ngOnInit() {
this.fetchPosts();
this.fetchHeadline();
}
This API request fetches homepage headline content. You can setup your own custom content fields to manage any content kind of content you need.
Hook this component up in Angular by adding ` to app.component.html and see it in action!
With our 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",
}
}
}
Here's how the whole hello-you.component.ts will look like:
import {Component, OnInit} from '@angular/core';
import {butterService} from '../services';
@Component({
selector: 'app-hello-you',
template: `
<h1>Hello-You</h1>
<p>
Headline:
{{headlines?.data?.homepage_headline}}
</p>
<p>
Sample Post:
{{posts? posts.data[0]?.url : 'no posts'}}
</p>
`,
styles: []
})
export class HelloYouComponent implements OnInit {
posts;
headlines;
constructor() {
}
ngOnInit() {
this.fetchPosts();
this.fetchHeadline();
}
private fetchHeadline() {
butterService.content.retrieve(['homepage_headline'])
.then((res) => {
console.log('Headline from ButterCMS');
console.log(res);
this.headlines = res.data;
});
}
private fetchPosts() {
butterService.post.list({
page: 1,
page_size: 10
})
.then((res) => {
console.log('Content from ButterCMS');
console.log(res);
this.posts = res.data;
});
}
}
This guide uses the Angular framework and Angular cli to generate all our components and package our application. We are using Angular Material for our look and feel, but feel free to use any other.
Let's get to the code.
Now we will build on top of what we have created so far.
In a terminal run
ng serve
Your localhost:4200 should be ready to serve your Angular page.
Under src/app look at the file called app-routing.module.ts
Initially it looks like this :
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
These components are generated by angular cli using ng g c <my-new-component>
Here are 8 components we will need to create:
ng g c blog-post-details --skipTests
ng g c blog-post-listing --skipTests
ng g c customer-details --skipTests
ng g c customer-listing --skipTests
ng g c faq --skipTests
ng g c feed --skipTests
ng g c home --skipTests
ng g c topav --skipTests
You will also need a shared
folder that contains a list of Angular Material modules
and a model
folder, that will contain our topNav models.
Since we are using Angular Material, you will need to provide a themes
folder that contains what theme we want to use for Angular Material.
Here is how your app-routing.module.ts
should look like:
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {FaqComponent} from './faq/faq.component';
import {HomeComponent} from './home/home.component';
import {FeedComponent} from './feed/feed.component';
import {HelloYouComponent} from './hello-you/hello-you.component';
import {BlogPostListingComponent} from './blog-post-listing/blog-post-listing.component';
import {CustomerListingComponent} from './customer-listing/customer-listing.component';
import {CustomerDetailsComponent} from './customer-details/customer-details.component';
import {BlogPostDetailsComponent} from './blog-post-details/blog-post-details.component';
const appRoutes: Routes = [
{path: 'customer', component: CustomerListingComponent},
{path: 'customer/:slug', component: CustomerDetailsComponent},
{path: 'faq', component: FaqComponent},
{path: 'blog', component: BlogPostListingComponent},
{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 {
}
We've already created all the components we need so far.
- Import butterService
- In OnInit hook, use butterService to get the list of customers
- Store results in
pages
variable and 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 CustomerListingComponent implements OnInit {
public pages: any[];
constructor() {
}
ngOnInit() {
butterService.page.list('customer_case_study')
.then((res) => {
this.pages = res.data.data;
});
}
}
<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>
In the customer details component
- Import butterService
- In OnInit hook, use butterService to get the customer page given the slug in the URL path
- Store results in
page
variable and markup (html) will be updated with the customer data
import {Component, OnInit} from '@angular/core';
import {Observable} from 'rxjs';
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 {
public page: any;
protected slug$: Observable<string>;
constructor(protected route: ActivatedRoute) {
}
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);
});
});
}
}
<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>
We can now navigate to the Customer Page via the list of all Customer Pages or directly via url.
In the faq component we need to setup the onInit hook.
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;
});
}
}
<mat-card>
<mat-card-title class="page-title">{{faq.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>
That's it! The values entered in the Butter dashboard will immediately update the content in our app.
To display posts we create a simple /blog
route in our 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.
In the blog-post-listing component, import butterService and load all posts using the onInit hook.
- import butterService
- Get all post onInit
import {Component, OnInit} from '@angular/core';
import {butterService} from '../services';
@Component({
selector: 'app-blog-post-listing',
templateUrl: './blog-post-listing.component.html',
styleUrls: ['./blog-post-listing.component.scss']
})
export class BlogPostListingComponent implements OnInit {
public posts: any[];
constructor() {
}
ngOnInit() {
butterService.post.list({
page: 1,
page_size: 10
}).then((res) => {
this.posts = res.data.data;
});
}
}
<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}}" alt="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>
blog-post-details will show a single post.
apps/blog-post-details.component.ts
- Import butterService
- In OnInit hook, use butterService to get the blog-post post given the slug in the URL path
- Store results in
post
variable and markup (html) will be updated with the customer data
import {Component, OnInit, ViewEncapsulation} from '@angular/core';
import {Observable} from 'rxjs';
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);
});
});
}
}
<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 our app is pulling all blog posts and we can navigate to individual posts.
However, our next/previous post buttons are not working. You should try showing the next/previous posts after this tutorial.
Now our app has a working blog that can be updated easily in the ButterCMS dashboard.
Use Butter's APIs for categories, tags, and authors to feature and filter content on your blog:
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()
}
Butter generates RSS, Atom, and sitemap XML markup. To use these on your blog, return the generated XML from the Butter API with the proper content type headers.
Create a file to see an example of what we get back from the API.
app/feed/feed.component.ts
import {Component, OnInit} from '@angular/core';
import {butterService} from '../services';
@Component({
selector: 'app-misc',
templateUrl: './feed.component.html',
styleUrls: ['./feed.component.scss']
})
export class FeedComponent implements OnInit {
protected rss;
protected atom;
protected sitemap;
constructor() {
}
ngOnInit() {
butterService.feed.retrieve('rss').then((res) => {
console.log(res.data.data);
this.rss = res.data.data;
});
butterService.feed.retrieve('atom').then((res) => {
console.log(res.data.data);
this.atom = res.data.data;
});
butterService.feed.retrieve('sitemap').then((res) => {
console.log(res.data.data);
this.sitemap = res.data.data;
});
}
}
<mat-card>
<mat-card-title class="page-title">RSS, Atom & Sitemap</mat-card-title>
<mat-divider></mat-divider>
<div class="feeds">
<mat-card>
<mat-card-title>RSS</mat-card-title>
<mat-card-content>{{rss}}</mat-card-content>
</mat-card>
<mat-card>
<mat-card-title>Atom</mat-card-title>
<mat-card-content>{{atom}}</mat-card-content>
</mat-card>
<mat-card>
<mat-card-title>Sitemap</mat-card-title>
<mat-card-content>{{sitemap}}</mat-card-content>
</mat-card>
</div>
<mat-divider></mat-divider>
<mat-card-footer>
<div class="page-footer">
<mat-icon>whatshot</mat-icon>
</div>
</mat-card-footer>
</mat-card>
Navigate to localhost:4200/rss
We can see ButterCMS passes back the entire xml need to create a feed or sitemap. However, this demo page will not work for our subscribers. We need to create xml files they can link to in their RSS readers.
This can be a challenge for a static deployed site built using Angular that has dynamic content.
Fortunately ButterCMS has Webhooks that we can utilizing to POST change notifications.
Setup up a listener webhook on your hosting that will trigger a rebuild or redeploy of your static site when it receives a POST request.
Setup a POST Webhook in the ButterCMS dashboard with the Event "Blog Post Create, Update, or Delete" and paste the listener url into Target URL field.
View our Angular Blog engine and Angular Full CMS for other examples of using ButterCMS with Angular.