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 HttpClient to communicate with the API

Create a simple API using JSON server

  • 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!
  • JSON serveropen in new window will get us a full fake REST API with zero coding!

JSON server

  • Create a folder json-server-local in the root folder of your project
  • Install JSON server in the project
npm install json-server@0.17.4

json-server@0.17.4

  • The version is very important here! A newer version still contains some bugs when adding an object!
  • Create a db.json file with some data in the json-server-local folder
{
    "articles": [
        {
            "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",
            "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"
        }
    ]
}
  • Execute the following command in the json-server-local folder to start your API
json-server --watch db.json

Tips

  • You can modify the npm start script so you start the json-server and the application simultaneously:
"scripts": {
    "ng": "ng",
    "start": "start npm run api && start ng serve -o --port 5878",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test",
    "api": "json-server --watch json-server-local\\db.json"
  },


 



 

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.
  • This way dependency injection will be applied when we're asking for the HttpClient in every component!
//app.config.ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { provideHttpClient } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), 
    provideHttpClient()]
};





 



 

  • Next we have to inject the HttpClient service in the constructor of the ArticleService
  • We also refactor our 2 methods to work with the HttpClient
//article.service.ts

import { Injectable } from '@angular/core';
import { Article } from './article';

import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

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

  constructor(private httpClient: HttpClient) {
  }

  getArticles(): Observable<Article[]> {
    return this.httpClient.get<Article[]>("http://localhost:3000/articles");
  }

  getArticleById(id: number): Observable<Article> {
    return this.httpClient.get<Article>("http://localhost:3000/articles/" + id);
  }

}





 
 






 
 


 



 



codedescription
constructor(private httpClient: HttpClient) { ... }injects the HttpClient service into a private httpClient property
this.httpClient.get<Article[]>("http://localhost:3000/articles");perform a HTTP GET request on the provided url
this.httpClient.get<Article>("http://localhost:3000/articles/" + 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, OnInit } from '@angular/core';
import { ArticleComponent } from '../article/article.component';
import { Article } from '../article';
import { ArticleService } from '../article.service';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-article-detail',
  standalone: true,
  imports: [ArticleComponent],
  templateUrl: './article-detail.component.html',
  styleUrl: './article-detail.component.css'
})
export class ArticleDetailComponent implements OnInit {
  article!: Article;

  constructor(private articleService: ArticleService, private route: ActivatedRoute) { }

  ngOnInit(): void {
    const articleId = this.route.snapshot.paramMap.get('id');
    if (articleId != null) {
      this.articleService.getArticleById(+articleId).subscribe(result => 
        this.article = result);
    }
  }
}






















 
 



  • The .subscribe(result => this.article = result) subscribes to the Observable. When the HTTP GET request is completed the result is assigned to the article property
  • The template is still working without code changes. This proves that we are doing a great job on separation of concerns!

Subscribe!

  • Without a .subscribe() the API call will never be executed!
  • We also have to update the HomeComponent to work correctly with the Observable
  • Here we will use a different approach for subscription using the async pipe
//home.component.ts
import { Component } from '@angular/core';
import { Article } from '../article';
import { ArticleService } from '../article.service';
import { ArticleComponent } from '../article/article.component';
import { Observable } from 'rxjs';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-home',
  standalone: true,
  imports: [ArticleComponent, CommonModule],
  templateUrl: './home.component.html',
  styleUrl: './home.component.css'
})
export class HomeComponent {
  articles$: Observable<Article[]> = new Observable<Article[]>();

  constructor(private articleService: ArticleService) { }

  ngOnInit(): void {
    this.articles$ = this.articleService.getArticles();
  }
}





 
 




 




 




 


  • We declare a new property articles$ for the Observable we get back from our ArticleService
  • Because of the strict TypeScript rules we have to initialise our Observable using the = new Observable<Article[]>()
  • Instead of subscribing to the Observable we assign it to our articles$ property

async

  • The async pipe is part of the CommonModule. We have to import it in our component to be able to work with the async pipe in the template!

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 an Observable
  • 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

Polling

  • Recall this phrase: Observables can return a stream of values to an observer over time
  • At the moment we only call our API once (on subscription) so we only get one stream back
    • When new articles are added to the database, we don't get them automatically
  • To prove the power of Observables we will implement a polling mechanism with only modifying 1 line of code
  • We want to keep calling the API every 3 seconds. Therefore we use 2 extra RxJS operators: timer and switchMap and we use the pipe method to chain them together
//article.service.ts

import { Observable, switchMap, timer } from 'rxjs';

...

getArticles(): Observable<Article[]> {
   return timer(1, 3000).pipe(switchMap(() => this.httpClient.get<Article[]>("http://localhost:3000/articles")));
}


 




 

codedescription
timer(1, 3000)Creates an Observable that starts emitting after a given duration (1 millisecond) and keeps emitting after each period of time (3 seconds)
.pipe( ... )chain together all the functions that take, and return an Observable
switchMap(() => switch to a new Observable, in this case our call to the API through the HttpClient
  • Check the Network tab in the Developer Tools and you will see that we make an API call every 3 seconds
polling
polling

db.json

  • You can also verify the polling by adding, changing or removing an article in the db.json file
  • Within 3 seconds the changes are reflected in your application!

Polling & Observables

  • This is only one possible solution for working with realtime data
  • Calling your API every n seconds could be not so ideal because of performance issues and data traffic
  • Having a lot of subscriptions on a lot of Observables can result in memory leaks. Later on we will discuss how we can safely avoid these issues, but for now:
    • async pipe : automatically unsubscribes if the component is destroyed
    • .subscribe() : you have to unsubscribe manually, preferrably in the ngOnDestroy method of the component