Add a headless CMS with live preview to Angular in 5 minutes

Contents

In this tutorial we will learn how to integrate the headless CMS Storyblok with Angular. Step by step we will create all the necessary components and develop the integration with the Storyblok SDK storyblok-js-client. At the end you will have a live preview to create content in Storyblok:

You can clone this tutorial at: https://github.com/storyblok/storyblok-angular-example

Let’s start with Angular

Requirements

To follow this tutorial please make sure you have everything installed and configured:

Setup the project

In this tutorial we will use the angular-cli 8.0. To check your cli version use this command ng version .
We can now start and create our project:

ng new angular-tutorial 

If all went well enter the commands:

cd angular-tutorial && ng serve

Wait for the server to compile and go to http://localhost:4200 . You should see something like this:

Awesome, now let's install the client:

npm install storyblok-js-client

Before we get to the code go to Storyblok and follow the steps below.
First create a new space:

Now copy your preview token:

Now let's create the service to call with the Storyblok API.

In src/app create the file storyblok.service.ts

import { Injectable } from '@angular/core';
import Client from 'storyblok-js-client';

@Injectable({
  providedIn: 'root'
})
export class StoryblokService {
  private sbClient = new Client({
    accessToken: 'Your_Preview_Token' // Add your token here
  });

  constructor() { }

  getStory(slug: string, params?: object): Promise<any> {
    return this.sbClient.getStory(slug, params)
      .then(res => res.data);
  }

  getStories(params?: object): Promise<any> {
    return this.sbClient.getStories(params)
      .then(res => res.data);
  }
}

Next create the directive storyblok.directive.ts in src/app :

import { Directive, ElementRef, Input } from '@angular/core';

@Directive({
  selector: '[appStoryblok]'
})
export class StoryblokDirective {

  @Input('appStoryblok') appStoryblok: string;

  constructor(private el: ElementRef) {

  }

  ngOnInit() {
    if (typeof this.appStoryblok === 'undefined') {
      return;
    }

    let options = JSON.parse(this.appStoryblok.replace('<!--#storyblok#', '').replace('-->', ''));

    this.el.nativeElement.setAttribute('data-blok-c', JSON.stringify(options));
    this.el.nativeElement.setAttribute('data-blok-uid', options.id + '-' + options.uid);
  }

}

We will need 4 components: teaser, grid, feature and page. The data for the components will be filled by the API. To create the components we will use the cli:

$ ng g component page
$ ng g component teaser
$ ng g component grid
$ ng g component feature

In order to render components dynamically you need to install an additional package:

$ npm install ng-dynamic-component --save

In the file src/app/app.component.html add the following code:

<div class="root">

  <div *ngIf="story.content">
    <ndc-dynamic [ndcDynamicComponent]="components[story.content.component]" [ndcDynamicInputs]="story.content">
    </ndc-dynamic>
  </div>
</div>

Let's create the file components.ts in src/app that will be in charge of loading our components.

import { PageComponent } from './page/page.component';
import { TeaserComponent } from './teaser/teaser.component';
import { GridComponent } from './grid/grid.component';
import { FeatureComponent } from './feature/feature.component';

let Components = {
  'page': PageComponent,
  'teaser': TeaserComponent,
  'grid': GridComponent,
  'feature': FeatureComponent
}

export { Components }

Update the code in the file src/app/app.module.ts to include the new components:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { DynamicModule } from 'ng-dynamic-component';
import { AppComponent } from './app.component';
import { StoryblokService } from './storyblok.service';
import { StoryblokDirective } from './storyblok.directive';
import { TeaserComponent } from './teaser/teaser.component';
import { GridComponent } from './grid/grid.component';
import { PageComponent } from './page/page.component';
import { FeatureComponent } from './feature/feature.component';

@NgModule({
  declarations: [
    AppComponent,
    PageComponent,
    StoryblokDirective,
    TeaserComponent,
    GridComponent,
    FeatureComponent
  ],
  imports: [
    BrowserModule,
    DynamicModule.withComponents([
      PageComponent,
      TeaserComponent,
      GridComponent,
      FeatureComponent
    ])
  ],
  providers: [
    StoryblokService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

To finish the component configuration open src/app/app.component.ts and add the following code:

import { Component, OnInit } from '@angular/core';
import { StoryblokService } from './storyblok.service';
import { Components } from './components';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  story = { content: null, name: '' };
  components = Components;

  constructor(private storyblokService: StoryblokService) {
    window.storyblok.init();
    window.storyblok.on(['change', 'published'], function () {
      location.reload(true)
    });
  }

  ngOnInit() {
    this.storyblokService.getStory('home', { version: 'draft' })
      .then(data => this.story = data.story);
  }
}

Great, we have almost everything ready. Now we will add the code to render content coming from the Storyblok API.

Rendering Components/Content

Page component

Open src/app/page/page.component.html and adding the following code:

<div>
  <div [appStoryblok]="_editable">
    <div *ngFor="let blok of body">
      <ndc-dynamic [ndcDynamicComponent]="components[blok.component]" [ndcDynamicInputs]="blok">
      </ndc-dynamic>
    </div>
  </div>
</div>

Modify the code of page.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { Components } from '../components';

@Component({
  selector: 'app-page',
  templateUrl: './page.component.html',
  styleUrls: ['./page.component.css']
})
export class PageComponent implements OnInit {
  components = Components;

  @Input() body: any[];
  @Input() _editable: any;

  constructor() { }

  ngOnInit() {
  }

}

Teaser Component

In the Teaser component src/app/teaser/teaser.component.html add the following code:

<div class="teaser" [appStoryblok]="_editable">
  <h1>
    {{ headline }}
  </h1>
</div>

Now open teaser.component.ts and paste following:

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-teaser',
  templateUrl: './teaser.component.html',
  styleUrls: ['./teaser.component.css']
})
export class TeaserComponent implements OnInit {
  @Input() headline: string;
  @Input() _editable: any;

  constructor() { }

  ngOnInit() {
  }

}

Grid component

Change src/app/grid/grid.component.html

<div class="grid" [appStoryblok]="_editable">
  <div *ngFor="let blok of columns">
    <ndc-dynamic [ndcDynamicComponent]="components[blok.component]" [ndcDynamicInputs]="blok">
    </ndc-dynamic>
  </div>
</div>

Change grid.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { Components } from '../components';

@Component({
  selector: 'app-grid',
  templateUrl: './grid.component.html',
  styleUrls: ['./grid.component.css']
})
export class GridComponent implements OnInit {
  components = Components;

  @Input() columns: any[];
  @Input() _editable: any;

  constructor() { }

  ngOnInit() {
  }

}

Feature component

Change src/app/feature/feature.component.html

<div class="column" [appStoryblok]="_editable">
  <h2>{{ name }}</h2>
</div>

Change feature.component.ts

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-feature',
  templateUrl: './feature.component.html',
  styleUrls: ['./feature.component.css']
})
export class FeatureComponent implements OnInit {
  @Input() name: string;
  @Input() _editable: string;

  constructor() { }

  ngOnInit() {
  }

}

Add the Javascript bridge to index.html in src/:

<script src="http://app.storyblok.com/f/storyblok-latest.js?t=Q2mIMuV6frKpnmQlOee0rAtt" type="text/javascript"></script>

Add some example styles:

<link rel="stylesheet" href="https://rawgit.com/DominikAngerer/486c4f34c35d514e64e3891b737770f4/raw/db3b490ee3eec14a2171ee175b2ee24aede8bea5/sample-stylings.css">

Now that everything is set define the server url in 2. Start your server to http://localhost:4200 as seen below:

Ready! You should now see the following:

Conclusion

In this tutorial we learned how to integrate the headless CMS Storyblok in Angular 8 with live editing. You can take a look at the code in this tutorial at: https://github.com/storyblok/storyblok-angular-example


About the author

Ademar Cardoso

Ademar Cardoso

Working as a front-end developer at Storyblok, passionate about new technologies and the open source community.


More to read...