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

Contents
    Try Storyblok

    Storyblok is the first headless CMS that works for developers & marketers alike.

    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 *ngIf="components" [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 { StoryblokService } from '../storyblok.service';
    
    @Component({
      selector: 'app-page',
      templateUrl: './page.component.html',
      styleUrls: ['./page.component.css']
    })
    export class PageComponent implements OnInit {
      components: any;
    
      constructor(private storyblok: StoryblokService) {
        import('src/app/components').then(cp => {
          this.components = cp.Components;
        });
      }
    
      @Input() body: any[];
      @Input() _editable: any;
    
      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 *ngIf="components" [ndcDynamicComponent]="components[blok.component]" [ndcDynamicInputs]="blok">
        </ndc-dynamic>
      </div>
    </div>
    

    Change grid.component.ts

    import { Component, OnInit, Input } from '@angular/core';
    import { StoryblokService } from '../storyblok.service';
    
    @Component({
      selector: 'app-grid',
      templateUrl: './grid.component.html',
      styleUrls: ['./grid.component.css']
    })
    
    export class GridComponent implements OnInit {
      components: any;
      constructor(private storyblok: StoryblokService) {
        import('src/app/components').then(cp => {
          this.components = cp.Components;
        });
      }
    
      @Input() columns: any[];
      @Input() _editable: any;
    
      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