Getting started with Contentful and Angular

Posted by James on September 7, 2019

I cut my teeth developing Drupal sites. I've got to say, I miss the open source vibe of the Drupal Cons and Camps. But it seems like the world has moved on, and you can knock something out with a mixture of Angular and Contentful pretty quickly.

At the moment this site is pretty straightforward. I'm using the content list endpoint to get the listings page, then the individual item lookup for the individual article pages. My Angular http service get function looks like this:

getContentItemByUrl(search) {  const path = `/spaces/{space}/environments/master/entries?access_token={accessToken}&content_type=story&fields.url=${search}`;  const url =  'https://cdn.contentful.com' + path;  return this.http.get(url, this.createHeaders());}

The Loop

WordPress developers might already be familiar with The Loop, which is a big while loop that looks for posts to display on the aggregator page of your site. The approach I've taken to headless CMS display is similar, only instead of looping through the list of posts we're looping through the content object.

The object returned from the getContentItemByUrl function looks like this:

"items": [        {            ...            "fields": {                "title": "Getting your Angular Universal site production ready",                "summary": "Want a quicker time to first paint...",                "bodyText": {                    "nodeType": "document",                    "data": {},                    "content": [                        {                            "nodeType": "paragraph",                            "content": [                                {                                    "nodeType": "text",                                    "value": "I've recently finished converting...",                                    "marks": [],                                    "data": {}                                }                            ],                        },                    ],                },            },        },    ]

You'll probably see that the content itself is quite heavily nested. Each paragraph is its own property within the object, and has a type, and associated markup and data. We want to loop through it, and based on each paragraph's type property, assign the data to a new component.

I've created an article component in my application. The component uses the getContentItemByUrl from the content service, then executes a switch statement on the results

for (const paragraph of content.fields.bodyText.content) {  switch (paragraph.nodeType) {    case 'paragraph':      ... deal with the paragraph  }}

Ramda to the rescue!

You might not particularly like the first line of code in the block above. What happens if there isn't a fields property? More to the point, what if much, much further down in the response object there isn't a fields.bodyText.content[0].content.nodeType property?

I've tried various ways to deal with this sort of nesting, and my favourite so far is a library called Ramda.

You can install it with npm, like:

npm install ramda

Then once it's installed, import it into the file where you need it:

import * as R from 'ramda';

You can then create an array that corresponds to the path that you want, use it to initialise Ramda's path function, then pass it your deeply nested object to recover the value you want safely.

I've created a service for flattening the content down and I've called it ContentFlattenerService. It's got a switch statement at the top which determines what part of the content object I'm looking for, then a series of functions to flatten the content down so we can use it. Here's the one I use for accessing the image IDs:

getImage(content: object): string {  let imageId = '';  const path = ['fields', 'image', 'sys', 'id'];  const ids = R.path(path);  imageId = ids(content);  return imageUrl;}

As you can see, we pass in the relevant part of the content object, construct a path array that corresponds to the deeply nested value, initialise Ramda's path function with it, then pass it the object to get the value. If, say, the object were structured object.fields.picture.sys.id we'd get an undefined, instead of a big red console error and a half-built page.