Skip to content

Integrate Angular with Storyblok

Use Storyblok to manage the content of your Angular application.

In the terminal, install the Angular CLI.

Terminal window
npm install -g @angular/cli

Create a new Angular project with SSR support, following the official installation page.

Terminal window
ng new my-storyblok-app --ssr
cd my-storyblok-app

Create a new blank space (opens in a new window) to follow the tutorial from scratch, or start from the core blueprint.

In the terminal, install the @storyblok/angular package.

Terminal window
npm install @storyblok/angular

To use environment variables in your Angular app, generate environments files with the Angular CLI.

Terminal window
ng generate environments

Add the Storyblok access token to the environment files in the src/environments folder. Set production to false in environment.ts file and to true in environment.production.ts file.

src/environments/environment.ts
export const environment = {
production: true,
accessToken: 'YOUR-ACCESS-TOKEN'
};
src/environments/environment.development.ts
export const environment = {
production: false,
accessToken: 'YOUR-ACCESS-TOKEN'
};

Update the app.config.ts file to configure the Storyblok Angular package.

src/app/app.config.ts
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { environment } from '../environments/environment';
import { provideStoryblok } from '@storyblok/angular';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(routes),
provideClientHydration(withEventReplay()),
provideStoryblok({
accessToken: environment.accessToken,
region: 'eu',
}),
],
};

The Storyblok Angular package provides features such as fetching content from the Content Delivery API, component registration, and real-time visual editing available across your project.

Create a new file src/app/routes/home.component.ts and add the following code.

src/app/routes/home.component.ts
import { Component, ChangeDetectionStrategy, inject, signal, OnInit } from '@angular/core';
import { StoryblokService, StoryblokComponent, type SbBlokData } from '@storyblok/angular';
@Component({
selector: 'app-home',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [StoryblokComponent],
template: `
<div>
<sb-component [sbBlok]="storyContent()" />
</div>
`,
})
export class HomeComponent implements OnInit {
private readonly storyblok = inject(StoryblokService);
readonly storyContent = signal<SbBlokData | null>(null);
async ngOnInit() {
const client = this.storyblok.getClient();
const { data } = await client.stories.get('home', {
query: { version: 'draft' },
});
this.storyContent.set(data?.story?.content ?? null);
}
}

StoryblokService provides access to the Storyblok API client to fetch story data. StoryblokComponent dynamically renders content type and nestable blocks. In this case, it looks for the content type block of the home story.

Create page.component.ts component to render all stories of the page content type, such as the home story.

src/app/components/page/page.component.ts
import { Component, ChangeDetectionStrategy, input } from '@angular/core';
import { type SbBlokData, StoryblokComponent } from '@storyblok/angular';
export interface PageBlok extends SbBlokData {
body?: SbBlokData[];
}
@Component({
selector: 'app-page',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [StoryblokComponent],
template: `
<div class="page">
<sb-component [sbBlok]="blok().body" />
</div>
`,
})
export class PageComponent {
// Angular signal
readonly blok = input.required<PageBlok>();
}

The PageComponent passes the array of blocks in the body block field to the StoryblokComponent, which dynamically renders each block. It uses Angular signal for its blok input, ensuring the template updates automatically when the input changes.

Stories might contain a body or similar field that consists of an array with several blocks of custom types (for example, Feature, Teaser, Grid) in it.

Create the code for these components as follows.

src/app/components/feature/feature.component.ts
import { Component, ChangeDetectionStrategy, input } from '@angular/core';
import { type SbBlokData } from '@storyblok/angular';
export interface FeatureBlok extends SbBlokData {
name?: string;
description?: string;
}
@Component({
selector: 'app-feature',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="feature">
<h3>{{ blok().name }}</h3>
<p>{{ blok().description }}</p>
</div>
`,
})
export class FeatureComponent {
readonly blok = input.required<FeatureBlok>();
}
src/app/components/teaser/teaser.component.ts
import { Component, ChangeDetectionStrategy, input } from '@angular/core';
import { type SbBlokData } from '@storyblok/angular';
export interface TeaserBlok extends SbBlokData {
headline?: string;
}
@Component({
selector: 'app-teaser',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="teaser">
<h1>{{ blok().headline }}</h1>
</div>
`,
})
export class TeaserComponent {
readonly blok = input.required<TeaserBlok>();
}
src/app/components/grid/grid.component.ts
import { Component, ChangeDetectionStrategy, input } from '@angular/core';
import { StoryblokComponent, type SbBlokData } from '@storyblok/angular';
export interface GridBlok extends SbBlokData {
columns?: SbBlokData[];
}
@Component({
selector: 'app-grid',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [StoryblokComponent],
template: `<sb-component class="grid" [sbBlok]="blok().columns" />`,
})
export class GridComponent {
readonly blok = input.required<GridBlok>();
}

Similar to page.component.tsgrid.component.ts passes the array of blocks in the columns block field to the StoryblokComponent.

Create a component registry for Storyblok components in storyblok.components.ts and use lazy loading to reduce the initial bundle size.

src/app/storyblok.components.ts
import { type StoryblokComponentsMap } from '@storyblok/angular';
export const storyblokComponents: StoryblokComponentsMap = {
page: () => import('./components/page/page.component').then((m) => m.PageComponent),
teaser: () => import('./components/teaser/teaser.component').then((m) => m.TeaserComponent),
grid: () => import('./components/grid/grid.component').then((m) => m.GridComponent),
feature: () => import('./components/feature/feature.component').then((m) => m.FeatureComponent),
};

Update app.config.ts to register Storyblok components. Pass the storyblokComponents registry to the withStoryblokComponents provider.

By default, Angular does not bind route data to component inputs.
Add withComponentInputBinding() to the app.config.ts file to allow resolved data to be passed directly as component inputs. withComponentInputBinding() automatically binds route resolver data to matching component inputs.

src/app/app.config.ts
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { environment } from '../environments/environment';
import { provideStoryblok, withStoryblokComponents } from '@storyblok/angular';
import { provideStoryblok } from '@storyblok/angular';
import { storyblokComponents } from './storyblok.components'
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(routes, withComponentInputBinding()),
provideRouter(routes),
provideClientHydration(withEventReplay()),
provideStoryblok(
{
accessToken: environment.accessToken,
region: 'eu',
},
withStoryblokComponents(storyblokComponents),
),
],
};

Update src/app/app.routes.ts file to define the routes, so that the root path / renders the HomeComponent.

src/app/app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './routes/home.component';
export const routes: Routes = [
{ path: '', component: HomeComponent },
];

Finally, remove the contents in src/app/app.html and add the router-outlet. The router uses it to render the component that matches the current URL based on the defined routes.

src/app/app.html
<router-outlet></router-outlet>

Run the server and visit the site in your browser.

Terminal window
npm start


Was this page helpful?

What went wrong?

This site uses reCAPTCHA and Google's Privacy Policy (opens in a new window) . Terms of Service (opens in a new window) apply.