Integrate Angular with Storyblok
Use Storyblok to manage the content of your Angular application.
Quickstart
Section titled “Quickstart”The fastest way to get started is using Storyblok’s Core Blueprint or CLI wizard:
Use Storyblok’s Core Blueprint to launch an Angular project, complete with a space, GitHub repository, and deployment environment.
Log in to Storyblok, select + Add Space → Start from Blueprint, and follow the steps.
Use Storyblok’s CLI to set up a new space and scaffold an Angular project. In the terminal, run:
storyblok create --template angularManual setup
Section titled “Manual setup”Alternatively, set up the project from scratch and configure your frontend code.
This guide has been tested with the following package versions:
@angular/core@21.2.0@storyblok/angularNode.js v22.13.0
-
Create a new space
Log in to Storyblok, select + Add Space → New Space, and follow the steps.
-
Create an Angular project
Install the Angular CLI, then create a new project with SSR support following the official installation guide.
Terminal window npm install -g @angular/cliTerminal window ng new my-storyblok-app --ssrcd my-storyblok-app -
Install
@storyblok/angularIn the terminal, install the package.
Terminal window npm install @storyblok/angular
Next, to use environment variables in your Angular app, generate environments files with the Angular CLI.
ng generate environmentsAdd 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.
export const environment = { production: true, accessToken: "YOUR-ACCESS-TOKEN",};export const environment = { production: false, accessToken: "YOUR-ACCESS-TOKEN",};Update the app.config.ts file to configure the Storyblok Angular package.
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.
Fetch a single story
Section titled “Fetch a single story”Create a new file src/app/routes/home.component.ts and add the following code.
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 and register blocks
Section titled “Create and register blocks”Create page.component.ts component to render all stories of the page content type, such as the home story.
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: `<sb-component class="page" [sbBlok]="blok().body" />`,})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 can have a body field that contains an array of custom type blocks, such as Feature, Teaser, and Grid.
Create the code for these components as follows.
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>();}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>();}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.ts, grid.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.
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.
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.
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.
<router-outlet></router-outlet>Run the server and visit the site in your browser.
npm startRelated resources
Section titled “Related resources”Was this page helpful?
This site uses reCAPTCHA and Google's Privacy Policy (opens in a new window).Terms of Service (opens in a new window) apply.
Get in touch with the Storyblok community