Angular And Contentful – Content Management For Single-Page Web Apps - In this tutorial you’ll learn how to integrate Contentful with your Angular 5/6 application. Contentful is a content management platform which allows you to structure and manage content in the cloud…

This content can then be used across multiple platform, e.g. the same content is consumed by a website and a mobile app. The Contentful website is available at https://www.contentful.com.

Contentful And Angular

In this tutorial you’ll learn how to use Contentful as the Content Management Backend for your Angular application. Traditionally the term content management is associated with Content Management Systems like WordPress. The Contentful approach is a more generic one. You can manage the content in the Contentful backend independent of your front-end / presentation layer.

Using a single-page application build with the Angular framework to present the content management by Contentful is only one option.

Create A Contentful Space And Define Content Types

To get started we first need to create a free Contentful account and then create a so called space in the Contentful backend. A space is a place where you keep all the content related to a single project in Contentful. For our sample project we’re going to create a new space in the Contentful backend first. To do so you first need to open the left side menu and then select the link Add Space. This is opening up the dialog which can be seen in the following screenshot:

Fill in a name for your new space (e.g. ngContentful01) and click on button Create Space to actually initiate that new space in the Contentful backend.

The next step is to define a new content type type for the space. This needs to be done prior to adding content because every piece of content needs to be described a content type first. The content type consists of fields, validations rules and appearance settings.

For the sample application we’re going to build in this tutorial we need to create a Course content time with the following fields:

  • Title
  • Course Image
  • Author
  • Description
  • Long Description
  • URL

The final content type definition can be seen in the following screenshot:

Having defined the content type you can switch to the Content view and add content based on the Course content type by using the Contentful editor:

Having added some courses sample data to Contentful we’re now ready to start building the Angular application and make use of that content in the next step.

Setting Up The Angular Project

Let’s initiate a new Angular project by using Angular CLI:

$ ng new ngContentful

The is setting up a default Angular project in the newly created directory ngContentful. Change into that new project directory:

$ cd ngContentful

and add the following dependencies via NPM:

$ npm install contentful marked bootstrap

  • contentful: JavaScript SDK for Contentful’s Content Delivery API.
  • marked: a library for transferring markdown code to HTML code
  • bootstrap: the Bootstrap front-end component library

Finally add the following line of code to styles.css to make Bootstrap’s CSS classes available in our application:

@import '~bootstrap/dist/css/bootstrap.min.css';

Creating A Service To Access Contentful Data

Now, that the project setup is ready we’re able to implement the application. First let’s add a new service to out Angular application which should contain the logic which is needed to retrieve data from Contentful:

$ ng g service contentful --module app

This is adding the file contentful.service.ts to with a default implementation of an Angular service class included. This default implementation needs to be enhanced with the code you can see in the following listing:

import { Injectable } from '@angular/core';
import { createClient, Entry } from 'contentful';
import { environment } from '../environments/environment';
import { Observable } from 'rxjs/Observable';


@Injectable()
export class ContentfulService {

  private client = createClient({
    space: environment.contentful.spaceId,
    accessToken: environment.contentful.token
  });

  constructor() { }

  getCourses(query?: object): Promise<Entry<any>[]> {
    return this.client.getEntries(Object.assign({
      content_type: 'course'
    }, query))
      .then(res => res.items);
  }

  getCourse(courseId): Promise<Entry<any>> {
    return this.client.getEntries(Object.assign({
     content_type: 'course'
    }, {'sys.id': courseId}))
      .then(res => res.items[0]);
  }
}

A connection to the Contentful service is created by calling the *createClient *function from the Contentful JavaScript library. To establish a connection this method gets a configuration object with two properties:

  • space
  • accessToken

For our application we’re storing Space ID and Access Token in environment.ts, so that we’re able to access both valus by using environment.contentful.spaceId and environment.contentful.token:

export const environment = {
  production: false,

  contentful: {
    spaceId: '[...]',
    token: '[...]'
  }
};

Having established a connection (available in variable client) the next step is to implement the following two service methods:

  • getCourses: this method is used to retrieve a list of all entries of content type *course *by excuting this.client.getEntries.
  • getCourse: this method is used to retrieve a single entry for a specific ID.

Creating CourseListComponent

The ContentfulService can now be used in all of our components. As the application should consists of two views (list of courses and course details page) we’re adding two components to our project. The first component is *CourseListComponent *and is generated by using Angular CLI again in the following way:

$ ng g component course-list

This command is adding the four new files to the project:

  • src/app/course-list/course-list.component.ts
  • src/app/course-list/course-list.component.html
  • src/app/course-list/course-list.component.css
  • src/app/course-list/course-list.component.spec.ts

Those files are containing the default implementation of a new component. Let’s start to add the needed functionality by first extending the class implementation in file course-list.component.ts:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { ContentfulService } from '../contentful.service';
import { Entry } from 'contentful';

@Component({
  selector: 'app-course-list',
  templateUrl: './course-list.component.html',
  styleUrls: ['./course-list.component.css']
})
export class CourseListComponent implements OnInit {

  courses: Entry<any>[] = [];

  constructor(
    private router: Router,
    private contentfulService: ContentfulService
  ) { }

  ngOnInit() {
    this.contentfulService.getCourses()
      .then(courses => this.courses = courses);
  }

  goToCourseDetailsPage(courseId) {
    console.log(courseId);
    this.router.navigate(['/course', courseId]);
  }

}

First of all you need to notice that two services are injected into the class constructor: Routerand ContentfulService. By using dependency injection we’re getting access to instances of both service class types.

In the *ngOnInit *component lifecycle method we’re calling this.contentfulService.getCourses() to retrieve a list of all courses. The data is returned as a promise, so we’re able to register a callback method with *then *which is executed once the promise is solved. In this case we’re assigning the returned data to class member courses.

Another class method is implemented: goToCourseDetailsPage(courseId). This method takes a specific course ID and then uses the Router service to navigate to the corresponding course detail’s page by executing this.router.navigate([‘/course’, courseId]).

Now let’s take a look at the template code of *CoursListComponent *in file course-list.component.html:

<div>
  <div class="alert alert-info" role="alert" *ngFor="let course of courses">
      <div class="d-flex flex-wrap flex-xl-nowrap flex-lg-nowrap flex-md-nowrap">
        <div class="p-2">
            <a href="{{ course.fields.url }}" target="_blank"><img src="{{ course.fields.courseImage.fields.file.url }}" class="rounded" width="196" /></a>
        </div>
        <div class="p-2">
          <h5 class="alert-heading"><a href="{{ course.fields.url }}" target="_blank">{{ course.fields.title}}</a></h5>
          <p><i>{{ course.fields.author }}</i></p>
          <div>{{ course.fields.description }}</div>

          <a href="{{ course.fields.url }}" target="_blank" class="btn btn-success">Go To Course</a>
          <button (click)="goToCourseDetailsPage(course.sys.id)" class="btn btn-info">Course Details</button>
        </div>
      </div>
    </div>
</div>

Here we’re using the *ngFor directive to iterate through the courses:

*ngFor="let course of courses"

For every course available the output is repeated. Bootstrap CSS classes are used to style the output. The course properties can be accessed via the field property, e.g.:

{{ course.fields.url }}

The click event of the Course Details button is connected to the goToCourseDetailsPagemethod. The current course ID which needs to be passed to this method is available via course.sys.id.

Adding A Router Configuration To AppModule

Before adding the next component (CourseDetailsComponent) to the project, let’s configure and activate the routing system. To do so let’s add the following import statement in app.module.ts:

import { RouterModule, Routes } from '@angular/router';

Next we need to add the router configuration to the file as well:

const routes: Routes = [ 
 { path: '', redirectTo: '/courses', pathMatch: 'full'}, 
 { path: 'courses', component: CourseListComponent}, 
 { path: 'course/:id', component: CourseDetailsComponent } 
];

Three routes are configured:

  • The default route (/) is being redirected to the /courses route
  • /courses is connected with component CourseListComponent, so that the course list is displayed
  • /course/:id is connected to component CourseDetailsComponent. Accessing this route displays the course details page for a specific ID.

To activate the route configuration for our project we need to add *RouterModule *to the imports array in the following way:

imports: [ 
 BrowserModule, 
 RouterModule.forRoot(routes) 
],

Finally the <router-outlet></router-outlet> element has to be part of the main component template in app.component.html. The element is a placeholder for the output which is generated by the component which is activated for the current route:

<div class="container"> 
 <router-outlet></router-outlet> 
</div>

Creating CourseDetailsComponent

Now that the routing configuration is available and activated for our Angular application, let’s continue with adding *CourseDetailsComponent *next:

$ ng g component course-details

Extend the default implementation in course-details.component.ts to comprise the code from the following listing:

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { ContentfulService } from '../contentful.service';
import { Entry } from 'contentful';

@Component({
  selector: 'app-course-details',
  templateUrl: './course-details.component.html',
  styleUrls: ['./course-details.component.css']
})
export class CourseDetailsComponent implements OnInit {

  course: Entry<any>;

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private contentfulService: ContentfulService
  ) { }

  ngOnInit() {
    const courseId = this.route.snapshot.paramMap.get('id');
    this.contentfulService.getCourse(courseId)
      .then((course) => {
        this.course = course;
        console.log(this.course);
      });
  }

  goToList() {
    this.router.navigate(['/courses']);
  }

}

Again we’re using dependency injection to inject services ActivatedRoute, Router and ContentfulService into the class. Furthermore two methods or implemented: *ngOnInit *and goToList.

The lifecycle method *ngOnInit *contains the code which is needed to retrieve the requested course ID from the URL via this.route.snapshot.paramMap.get('id') in the first step. In the second step this ID is then used to retrieve the course entry from Contentful by using the *ContentfulService *method getCourse.

Let’s complete the implementation of *CourseDetailsComponent *by adding the following template code to course-details.component.html:

<div *ngIf="course.fields">
  <a href="{{ course.fields.url }}" target="_blank"><img width="40%" style="margin: 10px" class="float-right rounded" src="{{course.fields.courseImage.fields.file.url}}"></a>
  <h2><a href="{{ course.fields.url }}" target="_blank">{{ course.fields.title }}</a></h2>
  <i>by {{ course.fields.author }}</i>
  <br><br>
  <div>{{ course.fields.description }}</div>
  <div>{{ course.fields.longDescription }}</div>
  <a href="{{ course.fields.url }}" target="_blank" class="btn btn-success">Go To Course</a>
  <button (click)="goToList()" class="btn btn-danger">Back To List</button>
</div>

Convert Markdown To HTML

The course fields *description *and *longDescription *have been configured in Contentful to contain Markdown code. In order to make sure that the Markdown text is displayed correctly in the browser we need to transfer it from Markdown to HTML. Therefore we will be used the marked JavaScript library which has already been installed. To be able to apply the transformation a new pipe is added to our project:

$ ng g pipe mdToHtml

The implementation is then added into file md-to-html.pipe.ts:

import { Pipe, PipeTransform } from '@angular/core';

import * as marked from 'marked';

@Pipe({
  name: 'mdToHtml'
})
export class MdToHtmlPipe implements PipeTransform {

  transform(value: string): any {
    return marked(value);
  }

}

The pipe can now be used in the template code of file course-list.component.html:

<div [innerHTML]="course.fields.description | mdToHtml"></div>

And in file course-details.component.html:

<div [innerHTML]="course.fields.description | mdToHtml"></div> 
<div [innerHTML]="course.fields.longDescription | mdToHtml"></div>

So that the markdown code is transferred to HTML and added to the output.

Result

The final result should now look like the following:

By default the /courses route is opened and the list of courses is presented. Clicking on thhttps://codingthesmartway.com/wp-content/uploads/2018/03/04-1.pnghttp://ngcontentful.surge.she Course Details button takes you to the corresponding details page:

Conclusion

By using the Contentful platform you can structure and manage your content in the cloud easily. Contentful providers a powerful back-end editor so that users can create and update content without hurdles. As Contentful is a generic approach to content management you can use that service across multiple platforms and presentation technologies.

In this tutorial you’ve learned how to use Contentful as the Content Management System for your Angular application. By using that approach you can combine the power of a modern single-page web application platform with a full featured cloud-based Content Management System.

#angular

Angular And Contentful – Content Management For Single-Page Web Apps
7 Likes49.20 GEEK