DEV Community

Rens Jaspers
Rens Jaspers

Posted on β€’ Edited on

31 2 2 3 2

Angular Guide for Beginners: Fetching Data from an API, Handling Errors, and Efficiently Managing Loading State

In this guide, we'll learn how to get data from an API using Angular, show a loading indicator while waiting for the data, and how to display errors nicely.

Using HttpClient in Angular

To fetch data from the web, Angular provides a tool called HttpClient. To use it, you need to add it to your project. The way you add it depends on how your project is set up.

With NgModules

If your project uses NgModules, add HttpClientModule to your AppModule file:

// app.module.ts
import { HttpClientModule } from "@angular/common/http";

@NgModule({
  declarations: [
    // your components here
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    // other modules here
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

With Standalone Components

For projects using standalone components, add HttpClient in your main file where you start your application:

// main.ts
bootstrapApplication(App, { providers: [provideHttpClient()] });
Enter fullscreen mode Exit fullscreen mode

Injecting HttpClient

After adding HttpClient, you need to include it in your component by adding it to the constructor.

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

@Component(...)
export class MyComponent {
  constructor(private http: HttpClient) {}
}
Enter fullscreen mode Exit fullscreen mode

Note: a good practice is to use HttpClient in services instead of directly in components. This makes your code neater.

Fetching Data from a Web Service

To get data from a web service, use the HttpClient.get() method. When you call this method, you can handle the data received or any errors that occur.

@Component(...)
export class MyComponent implements OnInit {
  data: any;

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http.get('https://api.mywebsite.com/data').subscribe((response) => {
      this.data = response;
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Showing a Loading Indicator

It's a good idea to show a loading spinner or icon while waiting for the data. You can do this by using an isLoading variable that is true when the data is being fetched and false once it's loaded. You use this variable in your template to show or hide the loading icon.

@Component(...)
export class MyComponent implements OnInit {
  data: any;
  isLoading: boolean;

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.isLoading = true;
    this.http.get('https://api.mywebsite.com/data').subscribe((response) => {
      this.data = response;
      this.isLoading = false;
    }, (error) => {
      this.isLoading = false;
      console.log('Error:', error);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Then, use the isLoading property to conditionally display a loading spinner in your component's template:

@if (isLoading) {
  <!-- Display your loading spinner or skeleton here -->
  Loading...
} @else {
  <!-- Display your data here -->
  {{ data }}
}
Enter fullscreen mode Exit fullscreen mode

Handling Errors

If there's a problem fetching the data, you should show an error message. You can add an error variable that stores error information and use it in your template to show an error message when needed:

@Component(...)
export class MyComponent implements OnInit {
  data: any;
  isLoading: boolean;
  error: string;

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.isLoading = true;
    this.http.get('https://api.mywebsite.com/data').subscribe((response) => {
      this.data = response;
      this.isLoading = false;
    }, (error) => {
      this.isLoading = false;
      this.error = 'An error occurred while fetching data';
      console.log('Error:', error);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

And in your template:

@if(isLoading {
  Loading...
} @else {
  @if(error) {
    {{ error.message }}
  } @else {
    {{ data }}
  }
}
Enter fullscreen mode Exit fullscreen mode

Having completed these steps, you now possess a basic setup for fetching data from an API in Angular, showing a loading spinner during data fetching, and handling errors.

Improving Data Fetching with the async Pipe

Angular has a feature called the async pipe that can make working with data from web services easier. It automatically handles subscribing and unsubscribing to data streams, which helps prevent memory leaks.

This is a pattern for using the async pipe you will see often:

@Component(...)
export class MyComponent {
  data$ = this.http.get('https://api.mywebsite.com/data');
}
Enter fullscreen mode Exit fullscreen mode
@if(data$ | async; as data) {
  {{ data }}
} @ else {
  Loading...
}
Enter fullscreen mode Exit fullscreen mode

Besides preventing memory leaks this approach also makes sure we don’t introduce side effects like setting an isLoading flag and storing the data in the data property of our components.

This is a big deal because effects make your code harder to understand and harder to debug and test: any part of the application can edit the isLoading or data property.

But be careful! Using the async pipe with raw data has major drawbacks such as incorrectly showing a loading icon when the data is actually loaded but is a falsy value (like 0 or null) or not handling errors properly.

A Better Approach: Loading State Object Stream

A better way to handle data loading and errors while using the async pipe is to create a special object that clearly indicates the loading state. This object can have a state property that shows whether the data is loading, loaded, or there was an error.

This is what a loading state object could look like:

interface Loading {
  state: "loading";
}

interface Loaded<T> {
  state: "loaded";
  data: T;
}

interface Errored {
  state: "error";
  error: Error;
}

type LoadingState<T = unknown> = Loading | Loaded | Errored;
Enter fullscreen mode Exit fullscreen mode

In your component, you can use it like this:

@Component()
export class MyComponent {
  data$ = this.http.get('https://api.mywebsite.com/data').pipe(
    map(data => ({ state: "loaded", data })),
    catchError(error => of({ state: "error", error })),
    startWith({state: "loading"})
  );
Enter fullscreen mode Exit fullscreen mode

When the data comes in, it is piped through the map operator to create a Loaded object. If there's an error, it's caught by the catchError operator and turned into an Errored object. The startWith operator is used to emit a Loading object when the stream starts.

In your template, you can use @switch to easily handle all the loading outcomes:

@if (data$ | async; as data) {
  @switch (data.state) {
    @case ("loading") {
      Loading...
    }
    @case ("error") {
      Error: {{ data.error.message }}
    }
    @case ("loaded") {
      {{ data.data }}
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This method is more clear and avoids the issues of directly using the data stream with the async pipe. It makes your code easier to understand and manage.

If you think the code in the last part is a bit hard, you're not alone. It includes some advanced topics from TypeScript and RxJS. But, you can easily make a function that changes your data stream into a loading state stream.

Here's how you can make any data stream show loading, loaded, or error states:

// to-loading-state-stream.ts
import { Observable, of } from "rxjs";
import { catchError, map, startWith } from "rxjs/operators";
import { LoadingState } from "./loading-state.interface";

export function toLoadingStateStream<T>(
  source$: Observable<T>,
): Observable<LoadingState<T>> {
  return source$.pipe(
    map((data) => ({ state: "loaded", data })),
    catchError((error) => of({ state: "error", error })),
    startWith({ state: "loading" }),
  );
}
Enter fullscreen mode Exit fullscreen mode

You can import this function and use it in any component:

@Component()
export class MyComponent {
  data$ = toLoadingStateStream(this.http.get("https://api.mywebsite.com/data"));
}
Enter fullscreen mode Exit fullscreen mode

If you still find this too tricky, I suggest checking out my library called ngx-load-with. It makes loading data very simple, even if you're not familiar with RxJS. It's as powerful and safe as the method we talked about but much easier to use. Check it out at github.com/rensjaspers/ngx-load-with and give it a star if it helps you!

Quadratic AI

Quadratic AI – The Spreadsheet with AI, Code, and Connections

  • AI-Powered Insights: Ask questions in plain English and get instant visualizations
  • Multi-Language Support: Seamlessly switch between Python, SQL, and JavaScript in one workspace
  • Zero Setup Required: Connect to databases or drag-and-drop files straight from your browser
  • Live Collaboration: Work together in real-time, no matter where your team is located
  • Beyond Formulas: Tackle complex analysis that traditional spreadsheets can't handle

Get started for free.

Watch The Demo πŸ“Šβœ¨

Top comments (1)

Collapse
 
bas_tenfeld_758591dcff04 profile image
Bas ten Feld β€’

Thank you Rens for the clear examples!

There is one issue, your error message is set as a string instead of an object.
I believe it should look like this (or like a new Error object):
this.error = { message: 'An error occurred while fetching data' };

PulumiUP 2025 image

PulumiUP 2025: Cloud Innovation Starts Here

Get inspired by experts at PulumiUP. Discover the latest in platform engineering, IaC, and DevOps. Keynote, demos, panel, and Q&A with Pulumi engineers.

Register Now

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay