Use Contentful in an Angular project
After its big rewrite Angular is one of the big players in single page application development. It's built to scale and enables developers to build large applications following conventions with speed. Very often single page applications are more than just business logic and you'll need a way to manage content and allow editors to make changes without touching code.
This guide will show you how to set up an Angular app from scratch and use data that is stored in Contentful. In case you're new to Angular you might want to consider doing the getting started tutorial first.
Create a new Angular app
It doesn't matter which JavaScript framework you prefer, most of them come with a CLI tool nowadays. These CLI tools help you get started and scaffolding new apps. For the case of Angular, it's Angular CLI which you can easily install via the command line.
$ npm install -g @angular/cli
Now you'll have the ng
command available in your terminal and you're ready to create a new Angular application. So let's jump right in and initialize a new project to make it ready for development.
$ ng new contentful-product-catalog-angular
$ cd contentful-product-catalog-angular
If you now run npm start
a development server will be started and you'll see that your Angular application is ready to fetch some content.
Have a look at the content model first
As the project name already reveals, the result of this project will be an app that deals with product catalog data.
When getting starting with Contentful, the web app enables you to get started quickly. This is done by providing example content models to give you an idea of what the best way to deal with structured data is. This tutorial is based on the example data set "product catalog".
You can either set up a new space now and initialize it with the catalog example data, or for the sake of simplicity just use a public Contentful space we provide for demo use cases. The credentials and configuration for the public space are provided in this tutorial.
Note: Creation of a space may result in additional charges if the free spaces available in your plan are exhausted.
Catalog data overview
The product catalog content model consists of three content types: a product, a category, and a brand. Product content entries can have relations to one or more categories and to one brand. Structuring the content model this way makes it easier to reuse entries.
If the concept of content modeling is new to you it's recommended to read the Contentful modeling basics and the concept of the Contentful data model first.
Create a Contentful service
To start fetching data from Contentful, start by creating an Angular service via the CLI. The g
is shorthand for the generate
command.
$ ng g service contentful
installing service
create src/app/contentful.service.spec.ts
create src/app/contentful.service.ts
WARNING Service is generated but not provided, it must be provided to be used
This creates a new contentful.service.ts
file.
// ./src/app/contentful.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class ContentfulService {
constructor() { }
}
To make your first requests use the Contentful JavaScript SDK and add it as a dependency to your project.
$ npm i --save contentful
You can then use the SDK in the service file you just created. Import the createClient
method of the contentful
dependency and you're ready to go.
Additionally, you can import Entry
which can come in handy when being in a TypeScript environment to give additional type information. The Entry
property is defined in the index.d.ts
file of the project.
import { createClient, Entry } from 'contentful';
The next step is to set up a config object which holds the needed information to retrieve data from the Contentful API. To use the Contentful API you need to define the id and access token of the space you want to fetch data from.
The ID of the product content type is included in the configuration object. As you're dealing with three different content types (product
, category
, brand
) this is needed to fetch particular entries like e.g. all of all products later on.
const CONFIG = {
space: 'wl1z0pal05vy',
accessToken: '0e3ec801b5af550c8a1257e8623b1c77ac9b3d8fcfc1b2b7494e3cb77878f92a',
contentTypeIds: {
product: '2PqfXUJwE8qSYKuM0U6w8M'
}
}
If you're following this tutorial you don't have to change anything right now as it uses the publicly available example space but if you want to play around with your own space follow the Authentication section to get your own space id and Content Delivery API access token.
Now it's time to fetch data!
To do so define the Contentful client in your service class and add a new method called getProducts
. This method will fetch the data using the getEntries
method of the SDK client. You can set the content_type
property inside of the query object with the product content type id to only retrieve products.
@Injectable()
export class ContentfulService {
private cdaClient = createClient({
space: CONFIG.space,
accessToken: CONFIG.accessToken
});
constructor() { }
getProducts(query?: object): Promise<Entry<any>[]> {
return this.cdaClient.getEntries(Object.assign({
content_type: CONFIG.contentTypeIds.product
}, query))
.then(res => res.items);
}
}
getEntries
returns a promise which will be resolved with meta information like the total number of fetched entries but also an Array of items
which includes the actual products you're interested in.
The ready to use service file then looks like:
// ./src/app/contentful.service.ts
import { Injectable } from '@angular/core';
// import Contentful createClient and type for `Entry`
import { createClient, Entry } from 'contentful';
// configure the service with tokens and content type ids
// SET YOU OWN CONFIG here
const CONFIG = {
space: 'wl1z0pal05vy',
accessToken: '0e3ec801b5af550c8a1257e8623b1c77ac9b3d8fcfc1b2b7494e3cb77878f92a',
contentTypeIds: {
product: '2PqfXUJwE8qSYKuM0U6w8M'
}
}
@Injectable()
export class ContentfulService {
private cdaClient = createClient({
space: CONFIG.space,
accessToken: CONFIG.accessToken
});
constructor() { }
getProducts(query?: object): Promise<Entry<any>[]> {
return this.cdaClient.getEntries(Object.assign({
content_type: CONFIG.contentTypeIds.product
}, query))
.then(res => res.items);
}
}
Afterwards you have to add the service provider to the AppModule
so that's it avaible in the component you'll set up in a minute.
// ./src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
// import the new service
import { ContentfulService } from './contentful.service';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
exports: [],
// make the provide available in the application
providers: [
ContentfulService
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
Add new routes
Now that you have the service ready it's time to define a route in the application that shows the products that are stored in Contentful. A single page application usually includes several routes and not every route will use the data coming from Contentful.
To have a clear encapsulation define a separate route which's only purpose is to show the products. At this stage this is only the beginning of your application so you have to define a redirect from the root to /products
so that the initial root HTTP call is handled.
// ./src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ContentfulService } from './contentful.service';
// import the router modules and the types for routes
import { RouterModule, Routes } from '@angular/router';
// define the available routes
const routes: Routes = [
{ path: '', redirectTo: '/products', pathMatch: 'full' },
{ path: 'products', component: ProductListComponent }
];
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
// import the new routes
RouterModule.forRoot(routes)
],
providers: [
ContentfulService
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
Create a product list page component to display Contentful entries
As you saw in the routing configuration, the routing for products
uses a ProductListComponent
. So let's create this one. And again the CLI tool has a command available for this use case.
$ ng g component product-list
This command will create a bunch of new files in ./src/app/product-list
. These files cover styling, templating and unit testing.
To use this new component it has to be registered in app.module.ts
and has to be included in the declarations
property of the module.
// ./src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { ContentfulService } from './contentful.service';
// import new component
import { ProductListComponent } from './product-list/product-list.component';
const routes: Routes = [
{ path: '', redirectTo: '/products', pathMatch: 'full' },
// define new component for '/products' route
{ path: 'products', component: ProductListComponent }
];
@NgModule({
declarations: [
AppComponent,
// add product component
ProductListComponent
],
imports: [
BrowserModule,
RouterModule.forRoot(routes)
],
exports: [],
providers: [
ContentfulService
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
Now you can adjust product-list.component.ts
to make use of the Contentful service you created. To add it to your component you can use Angular's dependency injection and simply add the service to the constructor
of the component.
constructor(private contentfulService: ContentfulService) { }
You should also define a private class property to the class which defines that this component will include a collection of several products.
private products: Entry<any>[] = [];
And then it's already time to get the data and fetch it in the initialization hook which is provided by Angular.
import { Component, OnInit } from '@angular/core';
ngOnInit() {
this.contentfulService.getProducts()
.then(products => this.products = products)
}
The final component file should look like this then.
// ./src/app/product-list/product-list.component.ts
import { Component, OnInit } from '@angular/core';
import { ContentfulService } from '../contentful.service';
import { Entry } from 'contentful';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
// define private class properties
private products: Entry<any>[] = [];
constructor(private contentfulService: ContentfulService) { }
// fetch data on init
ngOnInit() {
this.contentfulService.getProducts()
.then(products => this.products = products)
}
}
To prove that it's working you can adjust the template for the product list component and display the name of each product in a list.
<!-- ./app/product-list/product-list.component.html-->
<ul class="u-listReset">
<li *ngFor="let product of products">
{{ product.fields.productName }}
</li>
</ul>
Sum up
What you achieved now is that you've got one route that renders data coming from Contentful. This way editors and content creators can work in a comfortable authoring environment and you can deal with handy JSON data without worrying about content changes that need your attention.
More routes, more methods, and more functionality
This tutorial only showed the basic functionality of Contentful. If you're interested in more API calls and more components we also created a more advanced example project which includes routes to display product detail information and particular product categories. Have fun!
Additional resources
If Angular is not your thing we also provide several example apps that are based on the same data set:
Next steps
Not what you’re looking for? Try our FAQ.