How to work with an API
January 30, 2024About 6 min
How to work with an API
- In the end we want to
persistour data to adatabase - Because an
Angularapp lives entirely in thebrowser, we will need to work with anAPIto ensure persistence - When the
APIis running we can use Angular'shttpResourceandHttpClientto communicate with theAPI - While developing, we can fake an API using the Angular In-Memory Web API, then swap it out later for a real backend
Create a simple API using Angular In-Memory Web API
- The focus of this course is not about developing
APIs - We do want to learn how to communicate with them and how to handle the responses!
- angular-in-memory-web-api will get us a full fake
REST APIwith zero server setup!
Install
npm install angular-in-memory-web-api
In-Memory Web API
- Create a file
in-memory-data.service.tsin theappfolder of your project - Add the following code:
import { Injectable } from "@angular/core";
import { InMemoryDbService } from "angular-in-memory-web-api";
import { Article } from "./article";
@Injectable({ providedIn: "root" })
export class InMemoryDataService implements InMemoryDbService {
createDb() {
const articles: Article[] = [
{
id: 1,
title: "Title article",
subtitle: "Subtitle article",
imageUrl:
"https://images.pexels.com/photos/1202723/pexels-photo-1202723.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=100&w=134",
imageCaption: "caption image",
content: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Tenetur voluptas sequi voluptatum pariatur! Quae cumque
quidem dolor maxime enim debitis omnis nemo facilis sequi autem? Quae tenetur, repellat vero deleniti vitae
dolores? Cum tempore, mollitia provident placeat fugit earum, sint, quae iusto optio ea officiis consectetur sit
necessitatibus itaque explicabo?`,
author: "Michaƫl Cloots",
publishDate: "28/11/2020",
},
{
id: 2,
title: "Title article 2",
subtitle: "Subtitle article 2",
imageUrl:
"https://images.pexels.com/photos/3422964/pexels-photo-3422964.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=100&w=134",
imageCaption: "caption image 2",
content: `2 Lorem ipsum dolor sit amet consectetur adipisicing elit. Tenetur voluptas sequi voluptatum pariatur! Quae cumque
quidem dolor maxime enim debitis omnis nemo facilis sequi autem? Quae tenetur, repellat vero deleniti vitae
dolores? Cum tempore, mollitia provident placeat fugit earum, sint, quae iusto optio ea officiis consectetur sit
necessitatibus itaque explicabo?`,
author: "Florian Smeyers",
publishDate: "30/11/2020",
},
];
return { articles };
}
}
- Register the
HttpClientand the providers needed for theHttpClientInMemoryWebApiModulein theapp.config.ts
import { ApplicationConfig, importProvidersFrom, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideHttpClient } from '@angular/common/http';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZonelessChangeDetection(),
provideRouter(routes),
provideHttpClient(),
importProvidersFrom(HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, {
delay: 300
}))
]
};
delay
- The delay (in ms) is used to simulate latency
Using Angular's HttpClient to call API
- Time to refactor the
ArticleServicebecause we now want to get the articles from theAPI - In Angular we can use the
HttpClientservicefrom within theHttpClientModuleto easily perform ourHTTP requests - Before we can use the
HttpClientservicewe have to provide theHttpClientthroughout the entire application. This can be done in theapp.config.ts(we already did it when setting up the in memory web api). - This way dependency injection will be applied when we're asking for the
HttpClientin every component!
//app.config.ts
import { ApplicationConfig, importProvidersFrom, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideHttpClient } from '@angular/common/http';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZonelessChangeDetection(),
provideRouter(routes),
provideHttpClient(),
importProvidersFrom(HttpClientInMemoryWebApiModule.forRoot(InMemoryDataService, {
delay: 300
}))
]
};
- Next we have to
injecttheHttpClientservice. We can choose to- Inject it in the
constructorof theArticleService - Inject it through the
injectmethod provided in@angular/core
- Inject it in the
- We'll chose option 2 for all our dependency injection matters, so let's refactor our 2 methods to work with the
HttpClient
//article-service.ts
import { inject, Injectable } from "@angular/core";
import { Article } from "./article";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs";
@Injectable({
providedIn: "root",
})
export class ArticleService {
private apiUrl = "api/articles";
private httpClient = inject(HttpClient);
getArticles(): Observable<Article[]> {
return this.httpClient.get<Article[]>(this.apiUrl);
}
getArticleById(id: number): Observable<Article> {
return this.httpClient.get<Article>(`${this.apiUrl}/${id}`);
}
}
| code | description |
|---|---|
private httpClient = inject(HttpClient); | injects the HttpClient service into a private httpClient property |
this.httpClient.get<Article[]>(this.apiUrl)); | perform a HTTP GET request on the provided url |
this.httpClient.get<Article>(``${this.apiUrl}/${id}``); | perform a HTTP GET request on the provided url |
import { Observable } from 'rxjs'; | each HTTP request you perform with the HttpClient returns an Observable. This is a class defined in the rxjs library. More info on observables later! |
this.httpClient.get()
- Let's break down the
getmethod from theHttpClientgetArticles(): Observable<Article[]>: our method returns anObservableof anArticle Arrayget<Article[]>: we defineArticle[]betweenget<...>as the result type (generics). This result type is at the end wrapped up in anObservable
- You don't have to define a return type. In that case the result will always be of the type
Observable<Object>. Because we like to work with types (and it saves us some headaches later on) we prefer defining them!
Observables
- There is a lot to tell about
Observables - In this course we'll only discuss the topics about
Observablesthat come in handy within an Angular application - What you should know right now - RxJS is a framework for
reactive programmingwhich makes it very easy to writeasynchronouscode - AnObservableis basically a function that can return astream of valuesto anobserverover time - Astreamis a sequence of data values over time - In ourgetArticles()methodastreamis the articles we get back from theAPI- Because we are workingasynchronouslywe don't know when in time we will get the result - Therefore we always need one or moresubscribersin order to make theObservabledo its work! - Asubscribersubscribesto theObservablewhich allows him to execute acode blockwhen the result is ready!
Services
- The place in Angular where you call an
APIis aservice Componentsshould be as simple and stupid as possible, so we improve:- Readability
- Maintainability
- Testability
- Never write complex business logic or data access logic in your
components!
- In the
ArticleDetailComponentwe need to modify some code to work correctly with theObservablewe get back from ourservice
//article-detail.component.ts
import { Component, inject, OnInit } from "@angular/core";
import { ArticleComponent } from "../article-component/article-component";
import { Article } from "../article";
import { ArticleService } from "../article-service";
import { ActivatedRoute } from "@angular/router";
import { Observable } from "rxjs";
import { AsyncPipe } from "@angular/common";
@Component({
selector: "app-article-detail-component",
imports: [ArticleComponent, AsyncPipe],
templateUrl: "./article-detail-component.html",
styleUrl: "./article-detail-component.css",
})
export class ArticleDetailComponent implements OnInit {
public article$: Observable<Article> | undefined;
private readonly articleService = inject(ArticleService);
private readonly route = inject(ActivatedRoute);
ngOnInit(): void {
const articleId = this.route.snapshot.paramMap.get("id");
if (articleId != null) {
this.article$ = this.articleService.getArticleById(+articleId);
}
}
}
- A good practice is to store the result of your
service(httpClient) call, which is anObservable<Article>, in apropertysuffixed with$ - In our tempate we'll use the
asyncpipeto subscribe to theObservable. When theHTTP GET requestis completed theresultis assigned to thearticlevariable in the template
<!-- article-detail-component.html -->
@if(article$ | async; as article) {
<app-article-component
[article]="article"
[isDetail]="true"
></app-article-component>
}
async
- Without the
asyncpipe theAPIcall will never be executed! - Make sure to import the
AsyncPipein the imports array of the component:imports: [ArticleComponent, AsyncPipe]so you can use it in the template - When the HTTP call is done and the article is retrieved, the
@ifwill become true and theArticleComponentwill be initialised
- We also have to update the
HomeComponentto work correctly with theObservable
//home-component.ts
import { Component, inject } from "@angular/core";
import { ArticleService } from "../article-service";
import { ArticleComponent } from "../article-component/article-component";
import { AsyncPipe } from "@angular/common";
@Component({
selector: "app-home-component",
imports: [ArticleComponent, AsyncPipe],
templateUrl: "./home-component.html",
styleUrl: "./home-component.css",
})
export class HomeComponent {
private readonly articleService = inject(ArticleService);
articles$ = this.articleService.getArticles();
}
We declare a new
propertyarticles$for theObservablewe get back from ourArticleServicearticles$
A property/variable of the type
Observableis always suffixed with the$sign. This is not required, but most developers use this naming convention because it's easy to determine which property is anObservableThe
subscriptionis happening in thetemplateusing theasync pipe, thus we have to modify thetemplate
<h1 class="text-4xl">Welcome home</h1>
<div class="grid grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-3 mx-auto py-6">
@for (article of articles$ | async; track $index) {
<app-article [article]="article"></app-article>
}
</div>
- When the
@foris executed, theasync pipesubscribesto thearticles$Observableand returns the latest value it has emitted