In this tutorial, let’s now create the service that will encapsulate the code for communicating with Contentful. Go back to your command-line interface and run the following command to generate the service:

ng g s content

Create a content-types.ts file in the src/app/ folder and add the following code:

import * as CFRichTextTypes from "@contentful/rich-text-types";
import * as Contentful from "contentful";

export interface TypeJobListingFields {
    role?: Contentful.EntryFields.Symbol;
    description?: Contentful.EntryFields.Text;
    howToApply?: CFRichTextTypes.Block | CFRichTextTypes.Inline;
    organization?: Contentful.EntryFields.Symbol;
    date?: Contentful.EntryFields.Date;
    skills?: Contentful.EntryFields.Symbol[];
}

export type TypeJobListing = Contentful.Entry<TypeJobListingFields>;

This will be used to type the entries corresponding to our content model. We can either write the types manually or use automated tools for a more scalable approach:

Open the src/app/content.service.ts file and start by adding the following imports:

import { Injectable } from '@angular/core';
import { createClient } from 'contentful';
import { from } from 'rxjs';
import { environment } from '../environments/environment';
import { TypeJobListingFields } from './content-types';

Next, add the client property and call the createClient() function to create a client as follows:

@Injectable({
  providedIn: 'root'
})
export class ContentService {

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

Then, add the following method to fetch content from Contentful:

getJobListings(query?: object){
  return from(
  this.client.getEntries<TypeJobListingFields>(Object.assign({
   content_type: 'jobListing'
  }, query))
  );
}

We call the getEntries method of contentful to retreive the entries corresponding to our model content that we pass as the first argument of the method. We use Object.assign to create an object containing the content type and any other queries that we pass to Contentful.

The method also accepts a generic type that defines the type of the content that will be returned which is in our case TypeJobListingFields.

Note If you didn’t save your content type id somewhere, you can get it as follows: contentful->content model -> click your content model name -> right sidebar look for your content type id.

Also, add the following method to fetch a job listing by ID:

getJobListingById(jobListingId: string, query?: any){
 return from(
   this.client.getEntry<TypeJobListingFields>(jobListingId, query)
   );
}

Add routing and display the content

After implementing the service, we need to create a component to display the job listings fetched from the backend; and add routing. Go back to your command-line interface and run the following commands:

ng g c jobListings 
ng g c jobListing

This will generate the components’ files and add the components to the root application module.

Next, open the src/app/app-routing.module.ts file and start by importing the components as follows:

import { JobListingsComponent } from './job-listings/job-listings.component';
import { JobListingComponent } from './job-listing/job-listing.component';

Then, add the following routes:

const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'listings' },
  { path: 'listings', component: JobListingsComponent },
  { path: 'listing/:id', component: JobListingComponent }
];

Here we define three routes – the first route simply redirects the user to the second route which links to the JobListingsComponent.

You can read more about routing in Angular 14 from this article.

Open the src/app/job-listings/job-listings.component.ts file and import the service and the other symbols as follows:

import { Component, OnInit } from '@angular/core';
import { ContentService } from '../content.service';
import { TypeJobListingFields } from '../content-types';
import { Entry } from 'contentful';

Next, inside the component’s class define the following array to hold the fetched job listings:

jobListings: Entry<TypeJobListingFields>[] | undefined;

Next, inject the service via the component’s class as follows:

constructor(public contentService: ContentService) { }

Injecting the service makes it available to the component.

Then, define the following method for fetching the content:

getJobListings(){
  this.contentService.getJobListings()
  .subscribe({
     next: (entryCollection) => {
       this.jobListings = entryCollection.items;
       console.log(this.jobListings);
     }
 });
}

Then, call the previous method inside the ngOnInit() life-cycle method of the component:

ngOnInit(): void {
  this.getJobListings();
}

Life-cycle methods are methods that hook into key events of the component. You can find more information about them from this article.

In the same series:
[5] Transform data with Angular pipes
[4] Angular 14 Routing and RxJS switchMap
[3] Angular 14 Service for communicating with Contentful CMS
[2] Setup Angular 14 environment variables
[1] Build Angular 14 apps with Contentful headless CMS