DEV Community

Rens Jaspers
Rens Jaspers

Posted on • Updated on

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

In this tutorial, we'll explore how to retrieve data from an API using Angular, manage the loading state, and show a loading spinner to enhance user experience during data fetch operations. We'll also delve into error handling and how to visually represent errors in the user interface.

Importing and Injecting HttpClient

To start, we'll need to make use of the HttpClient module, an integral part of Angular's common HTTP package that enables HTTP requests. First, import the module into your root module, typically AppModule:

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

Next, inject the HttpClient into your component as follows:

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

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

Note: In a practical application, we typically use HttpClient within services rather than directly inside our components, then inject those services into our components. This strategy enhances the maintainability and testability of the code.

Fetching Data from an API

Next, we'll fetch data from an API using the HttpClient.get() method.

@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;
    }, (error) => {
      console.log('Error:', error);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Displaying a Loading Spinner

While the data is in the process of being fetched, it's important to provide the user with visual feedback. Let's create a simple loading template that's visible when the data is loading and hidden once the data has been fetched.

First, add an isLoading property to your component:

@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:

<ng-container *ngIf="isLoading">
  <!-- Display your loading spinner or skeleton here -->
  Loading...
</ng-container>

<ng-container *ngIf="!isLoading && data">
  <!-- Display your data here -->
  {{ data | json }}
</ng-container>
Enter fullscreen mode Exit fullscreen mode

Error Handling

Lastly, let's add error handling to our data fetch operation. Introduce a new error property that will be set if an error arises during the fetch operation.

@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

In your component's template, you can now exhibit an error message to the user if an error occurred:

<ng-container *ngIf="isLoading">
  Loading...
</ng-container>

<ng-container *ngIf="error">
  {{ error }}
</ng-container>

<ng-container *ngIf="!isLoading && !error && data">
  <!-- Display your data here -->
  {{ data | json }}
</ng-container>
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.

Simplifying Things: Streamlining Loading State

Our component currently tracks the loading state with multiple properties (isLoading and error), and our template has several ngIf statements. While functional, this adds complexity and potential for errors, plus every ngIf statement requires additional testing. To streamline our code, we can use Angular techniques.

One such technique combines the async pipe with ngIf, automatically subscribing to an Observable and updating our view. It also unsubscribes automatically when the component is destroyed, preventing memory leaks and boosting performance if you enable onPush change detection.

To achieve this, let's modify how we fetch data. We'll move our HTTP request into a new property, data$, using the pipe operator to handle errors.

import { of, BehaviorSubject } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Component({
  // rest of the component metadata...
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent implements OnInit {
  data$: Observable<any>;
  error$ = new BehaviorSubject<string>(null);

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.data$ = this.http.get('https://api.mywebsite.com/data').pipe(
      catchError(error => {
        this.error$.next('An error occurred while fetching data');
        console.log('Error:', error);
        return of(null);
      })
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

In the template, we can use ngIf, async, and else to manage the loading state, display data, or show the loading spinner:

<ng-container *ngIf="data$ | async as data; else loading">
  <!-- Display your data here -->
  {{ data | json }}
</ng-container>

<ng-template #loading>
  <!-- Display your loading spinner here -->
  Loading...
</ng-template>

<ng-container *ngIf="error$ | async as error">
  {{ error }}
</ng-container>
Enter fullscreen mode Exit fullscreen mode

Here, we use ng-container to hold our *ngIf directive. It's a logical container used to group nodes but is not rendered in the DOM. ng-template is a template element used with structural directives like *ngIf.

By adopting this approach, we've eliminated one ngIf and the isLoading property from our component, simplifying our code. Furthermore, the async pipe handles unsubscribing from the observable, averting potential memory leaks.

Striving for Better: Further Simplification and Drawbacks

While we've made significant improvements with the async pipe and *ngIf else technique, there's still room for optimization. Our current solution necessitates manual error catching and an additional ngIf statement. It also makes an assumption that could potentially cause bugs: it equates falsy data (like false, "", null, undefined, 0) with a loading state.

For instance, if the API response is legitimately falsy, our implementation might fail. Consider an endpoint that might return 0—a valid response but also falsy in JavaScript. In such cases, our loading spinner would be displayed indefinitely, incorrectly interpreting 0 as an ongoing loading operation.

Moreover, manually catching errors, as we're currently doing, can be tedious and prone to errors. A more declarative approach would enhance code readability and reduce mistakes.

In the next section, we'll explore a solution that addresses these challenges.

Ultimate Solution: *ngxLoadWith

*ngxLoadWith is the ultimate solution to streamline and optimize the loading state process. *ngxLoadWith is an Angular structural directive, similar to *ngIf, that automatically manages all the loading state tracking and template handling for you. This tool not only brings convenience but also boosts performance and maintains a lightweight profile with zero dependencies.

Importantly, *ngxLoadWith correctly handles falsy values. So, even if your API response is a falsy value like 0, the loading states will be accurately represented. It also takes care of automatic Observable unsubscription and change detection, making it a robust tool for optimizing application performance. With *ngxLoadWith, you can enable the OnPush change detection strategy without a second thought.

With this library, you can keep your components clean and focused, free from loading-related logic. It's a truly declarative and expressive approach to handling loading states in Angular.

To install ngx-load-with, run the following command:

npm install ngx-load-with
Enter fullscreen mode Exit fullscreen mode

To use ngx-load-with, import the NgxLoadWithModule module in your Angular module:

import { NgxLoadWithModule } from "ngx-load-with";

@NgModule({
  imports: [NgxLoadWithModule, ...],
  declarations: [MyComponent],
  ...
})
export class MyModule {}
Enter fullscreen mode Exit fullscreen mode

Basic Usage

To load data from an Observable and display it in your template, use:


<ng-container *ngxLoadWith="data$ as data">
  <!-- Display your data here -->
  {{ data | json }}
</ng-container>

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

  constructor(private http: HttpClient) {}
}
Enter fullscreen mode Exit fullscreen mode

Loading and Error Templates

To display a loading message while data is being loaded and an error message if an error occurs, use:

<ng-container *ngxLoadWith="data$ as data; loadingTemplate: loading; errorTemplate: error">
  <!-- Display your data here -->
  {{ data | json }}
</ng-container>

<ng-template #loading>
  Loading...
</ng-template>

<ng-template #error let-error>
  {{error.message}}
</ng-template>
Enter fullscreen mode Exit fullscreen mode

See it in action in this Live Example

You can find more information about the *ngxLoadWith, including detailed documentation and more usage examples, on GitHub.

If *ngxLoadWith proves useful in your projects, please consider giving it a 🌟 star on GitHub. Your support helps bring attention to the library and contributes to its continued development.

Through this tutorial, you've learned to effectively manage loading states and errors in Angular. As you continue to build and refine your applications, remember these techniques to provide a seamless, user-friendly experience. Happy coding!

Latest comments (0)