Overview
This integration guide shows you how to how to update your existing project to:- install the ButterCMS package
- instantiate ButterCMS
- create components to fetch and display each of the three ButterCMS content types: Pages, Collections, and Blog Posts.
In order for the snippets to work, you’ll need to setup your dashboard content schemas inside of ButterCMS first.
Installation
- npm
- yarn
- pnpm
npm install buttercms
yarn add buttercms
pnpm add buttercms
environment.ts:
// src/environments/environment.ts
export const environment = {
production: false,
butterCmsApiToken: 'your_api_token',
};
Initialize the client
Create an Angular service to wrap the ButterCMS SDK:// src/app/services/butter.service.ts
import { Injectable } from '@angular/core';
import Butter from 'buttercms';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
})
export class ButterService {
private butter = Butter(environment.butterCmsApiToken);
getPage(pageType: string, slug: string, params?: object) {
return this.butter.page.retrieve(pageType, slug, params);
}
getContent(keys: string[], params?: object) {
return this.butter.content.retrieve(keys, params);
}
getPosts(params?: object) {
return this.butter.post.list(params);
}
getPost(slug: string) {
return this.butter.post.retrieve(slug);
}
}
For complete SDK documentation including all available methods and configuration options, see the JavaScript SDK Reference.
Pages
// src/app/pages/landing/landing.page.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ButterService } from '../../services/butter.service';
interface PageFields {
headline: string;
subheadline: string;
hero_image: string;
body: string;
}
@Component({
selector: 'app-landing',
templateUrl: './landing.page.html',
})
export class LandingPage implements OnInit {
page: { fields: PageFields } | null = null;
loading = true;
error: string | null = null;
constructor(
private route: ActivatedRoute,
private butter: ButterService
) {}
async ngOnInit() {
const slug = this.route.snapshot.paramMap.get('slug') || 'home';
try {
const response = await this.butter.getPage('landing-page', slug);
this.page = response.data.data;
} catch {
this.error = 'Page not found';
} finally {
this.loading = false;
}
}
}
<!-- src/app/pages/landing/landing.page.html -->
<ion-header>
<ion-toolbar>
<ion-title>{{ page?.fields?.headline }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div *ngIf="loading" class="ion-text-center ion-padding">
<ion-spinner></ion-spinner>
</div>
<div *ngIf="error" class="ion-padding">
<p>{{ error }}</p>
</div>
<div *ngIf="page && !loading" class="ion-padding">
<h1>{{ page.fields.headline }}</h1>
<p>{{ page.fields.subheadline }}</p>
<img
*ngIf="page.fields.hero_image"
[src]="page.fields.hero_image"
[alt]="page.fields.headline"
/>
<div [innerHTML]="page.fields.body"></div>
</div>
</ion-content>
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{
path: 'page/:slug',
loadChildren: () =>
import('./pages/landing/landing.module').then((m) => m.LandingPageModule),
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
Collections
// src/app/pages/brands/brands.page.ts
import { Component, OnInit } from '@angular/core';
import { ButterService } from '../../services/butter.service';
interface Brand {
name: string;
logo: string;
description: string;
}
@Component({
selector: 'app-brands',
templateUrl: './brands.page.html',
})
export class BrandsPage implements OnInit {
brands: Brand[] = [];
loading = true;
constructor(private butter: ButterService) {}
async ngOnInit() {
try {
const response = await this.butter.getContent(['brands']);
this.brands = response.data.data['brands'];
} finally {
this.loading = false;
}
}
}
<!-- src/app/pages/brands/brands.page.html -->
<ion-header>
<ion-toolbar>
<ion-title>Our Brands</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div *ngIf="loading" class="ion-text-center ion-padding">
<ion-spinner></ion-spinner>
</div>
<ion-list *ngIf="!loading">
<ion-item *ngFor="let brand of brands">
<ion-thumbnail slot="start">
<img [src]="brand.logo" [alt]="brand.name" />
</ion-thumbnail>
<ion-label>
<h2>{{ brand.name }}</h2>
<p [innerHTML]="brand.description"></p>
</ion-label>
</ion-item>
</ion-list>
</ion-content>
Dynamic components
Component registry
// src/app/components/component-renderer/component-renderer.component.ts
import {
Component,
Input,
ComponentRef,
ViewChild,
ViewContainerRef,
OnChanges,
SimpleChanges,
} from '@angular/core';
import { HeroComponent } from '../hero/hero.component';
import { FeaturesComponent } from '../features/features.component';
import { CtaComponent } from '../cta/cta.component';
interface ButterComponent {
type: string;
fields: Record<string, any>;
}
const componentMap: Record<string, any> = {
hero: HeroComponent,
features: FeaturesComponent,
cta: CtaComponent,
};
@Component({
selector: 'app-component-renderer',
template: `<ng-container #container></ng-container>`,
})
export class ComponentRendererComponent implements OnChanges {
@Input() components: ButterComponent[] = [];
@ViewChild('container', { read: ViewContainerRef, static: true })
container!: ViewContainerRef;
ngOnChanges(changes: SimpleChanges) {
if (changes['components']) {
this.renderComponents();
}
}
private renderComponents() {
this.container.clear();
this.components.forEach((item) => {
const componentClass = componentMap[item.type];
if (!componentClass) return;
const ref: ComponentRef<any> = this.container.createComponent(componentClass);
Object.assign(ref.instance, item.fields);
});
}
}
Example Component
// src/app/components/hero/hero.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-hero',
template: `
<section class="hero ion-padding">
<h1>{{ headline }}</h1>
<p>{{ subheadline }}</p>
<ion-button *ngIf="button_label" [href]="button_url">{{ button_label }}</ion-button>
<img *ngIf="image" [src]="image" [alt]="headline" />
</section>
`,
})
export class HeroComponent {
@Input() headline = '';
@Input() subheadline = '';
@Input() image = '';
@Input() button_label = '';
@Input() button_url = '';
}
Blog
- Blog Post List
- Single Blog Post
// src/app/pages/blog/blog.page.ts
import { Component, OnInit } from '@angular/core';
import { ButterService } from '../../services/butter.service';
interface Post {
slug: string;
title: string;
summary: string;
published: string;
author: { first_name: string; last_name: string };
}
@Component({
selector: 'app-blog',
templateUrl: './blog.page.html',
})
export class BlogPage implements OnInit {
posts: Post[] = [];
nextPage: number | null = null;
loading = true;
constructor(private butter: ButterService) {}
async ngOnInit() {
await this.loadPosts(1);
}
async loadPosts(page: number) {
this.loading = true;
try {
const response = await this.butter.getPosts({ page, page_size: 10 });
this.posts = response.data.data;
this.nextPage = response.data.meta.next_page;
} finally {
this.loading = false;
}
}
}
<!-- src/app/pages/blog/blog.page.html -->
<ion-header>
<ion-toolbar>
<ion-title>Blog</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div *ngIf="loading" class="ion-text-center ion-padding">
<ion-spinner></ion-spinner>
</div>
<ion-list *ngIf="!loading">
<ion-item
*ngFor="let post in posts"
[routerLink]="['/blog', post.slug]"
button
>
<ion-label>
<h2>{{ post.title }}</h2>
<p>By {{ post.author.first_name }} {{ post.author.last_name }}</p>
<p [innerHTML]="post.summary"></p>
</ion-label>
</ion-item>
</ion-list>
<div *ngIf="nextPage" class="ion-padding">
<ion-button expand="block" (click)="loadPosts(nextPage)">Load More</ion-button>
</div>
</ion-content>
// src/app/pages/blog-post/blog-post.page.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ButterService } from '../../services/butter.service';
interface Post {
title: string;
body: string;
published: string;
featured_image: string;
author: { first_name: string; last_name: string };
}
@Component({
selector: 'app-blog-post',
templateUrl: './blog-post.page.html',
})
export class BlogPostPage implements OnInit {
post: Post | null = null;
loading = true;
error: string | null = null;
constructor(
private route: ActivatedRoute,
private butter: ButterService
) {}
async ngOnInit() {
const slug = this.route.snapshot.paramMap.get('slug')!;
try {
const response = await this.butter.getPost(slug);
this.post = response.data.data;
} catch {
this.error = 'Post not found';
} finally {
this.loading = false;
}
}
}
<!-- src/app/pages/blog-post/blog-post.page.html -->
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button defaultHref="/blog"></ion-back-button>
</ion-buttons>
<ion-title>{{ post?.title }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div *ngIf="loading" class="ion-text-center ion-padding">
<ion-spinner></ion-spinner>
</div>
<div *ngIf="error" class="ion-padding">
<p>{{ error }}</p>
</div>
<article *ngIf="post && !loading" class="ion-padding">
<h1>{{ post.title }}</h1>
<p>
By {{ post.author.first_name }} {{ post.author.last_name }}
</p>
<img
*ngIf="post.featured_image"
[src]="post.featured_image"
[alt]="post.title"
style="width: 100%"
/>
<div [innerHTML]="post.body"></div>
</article>
</ion-content>
Resources
JavaScript SDK
Complete SDK reference
GitHub Repository
View source code
Content API
REST API documentation