Skip to main content

How to work with an API

January 30, 2024About 6 min

How to work with an API

  • In the end we want to persist our data to a database
  • Because an Angular app lives entirely in the browser, we will need to work with an API to ensure persistence
  • When the API is running we can use Angular's httpResource and HttpClient to communicate with the API
  • 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-apiopen in new window will get us a full fake REST API with zero server setup!

Install

npm install angular-in-memory-web-api

In-Memory Web API

  • Create a file in-memory-data.service.ts in the app folder 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 HttpClient and the providers needed for the HttpClientInMemoryWebApiModule in the 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
    }))
  ]
};













 
 
 
 


delay

  • The delay (in ms) is used to simulate latency

Using Angular's HttpClient to call API

  • Time to refactor the ArticleService because we now want to get the articles from the API
  • In Angular we can use the HttpClient service from within the HttpClientModule to easily perform our HTTP requests
  • Before we can use the HttpClient service we have to provide the HttpClient throughout the entire application. This can be done in the app.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 HttpClient in 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 inject the HttpClient service. We can choose to
    1. Inject it in the constructor of the ArticleService
    2. Inject it through the inject method provided in @angular/core
  • 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}`);
  }
}
codedescription
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 get method from the HttpClient
    • getArticles(): Observable<Article[]> : our method returns an Observable of an Article Array
    • get<Article[]> : we define Article[] between get<...> as the result type (generics). This result type is at the end wrapped up in an Observable
  • 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 Observables that come in handy within an Angular application
  • What you should know right now - RxJSopen in new window is a framework for reactive programming which makes it very easy to write asynchronous code - An Observable is basically a function that can return a stream of values to an observer over time - A stream is a sequence of data values over time - In our getArticles() method a stream is the articles we get back from the API - Because we are working asynchronously we don't know when in time we will get the result - Therefore we always need one or more subscribers in order to make the Observable do its work! - A subscriber subscribes to the Observable which allows him to execute a code block when the result is ready!

Services

  • The place in Angular where you call an API is a service
  • Components should 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 ArticleDetailComponent we need to modify some code to work correctly with the Observable we get back from our service
//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 an Observable<Article>, in a property suffixed with $
  • In our tempate we'll use the async pipe to subscribe to the Observable. When the HTTP GET request is completed the result is assigned to the article variable 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 async pipe the API call will never be executed!
  • Make sure to import the AsyncPipe in 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 @if will become true and the ArticleComponent will be initialised
  • We also have to update the HomeComponent to work correctly with the Observable
//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 property articles$ for the Observable we get back from our ArticleService

    articles$

    A property/variable of the type Observable is 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 anObservable

  • The subscription is happening in the template using the async pipe, thus we have to modify the template

<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 @for is executed, the async pipe subscribes to the articles$ Observable and returns the latest value it has emitted