Building a Robust HTTP Client in Angular
This challenge focuses on creating a reusable and well-structured HTTP client service in Angular. You will leverage Angular's HttpClient module to fetch data from a mock API, handle potential errors, and present the data effectively. This is a fundamental skill for any Angular developer, enabling seamless communication with backend services.
Problem Description
Your task is to create an Angular service that acts as an HTTP client. This service will be responsible for making requests to a simulated API, retrieving data, and handling common HTTP scenarios like successful responses and errors.
Key Requirements:
- Create an Angular Service: Define a new Angular service (e.g.,
ApiService) that encapsulates all HTTP-related logic. - Inject
HttpClient: Ensure your service correctly injects Angular'sHttpClientmodule for making requests. - Implement Data Fetching: Create a method within your service to fetch a list of "items" from a specified API endpoint (e.g.,
/api/items). - Handle Successful Responses: When a request is successful, the service should return the fetched data.
- Handle Errors: Implement error handling for HTTP requests. This includes catching network errors and non-2xx HTTP status codes. The service should provide a way for components to react to these errors.
- Type Safety: Use TypeScript interfaces to define the structure of the data being fetched and returned by the service.
- Reusability: The service should be designed to be easily reusable by multiple Angular components.
Expected Behavior:
- When a component calls a method on the
ApiServiceto fetch items, the service should make an HTTP GET request. - If the request is successful, the data (an array of items) should be returned.
- If an error occurs during the request (e.g., network issue, server error), the service should not crash but should signal the error to the caller in a manageable way.
Edge Cases to Consider:
- What happens if the API endpoint is unavailable?
- What if the API returns an unexpected data format? (For this challenge, assume the mock API will return data matching your defined interface).
Examples
Let's assume we have a mock API endpoint http://localhost:3000/api/items that returns an array of objects, each representing an item.
Example 1: Successful Data Fetch
// Assume ApiService has a method called 'getItems()'
// And a component subscribes to it.
// Inside the component:
this.apiService.getItems().subscribe(
(items) => {
console.log('Fetched items:', items);
// items will be an array of Item objects (e.g., [{ id: 1, name: 'Apple' }, { id: 2, name: 'Banana' }])
},
(error) => {
console.error('Error fetching items:', error);
}
);
// Expected Output (in the console):
// Fetched items: [ { id: 1, name: 'Apple' }, { id: 2, name: 'Banana' } ]
Example 2: Error Handling (e.g., 404 Not Found)
// Assume ApiService has a method called 'getItems()'
// And a component subscribes to it, but the endpoint is not found.
// Inside the component:
this.apiService.getItems().subscribe(
(items) => {
console.log('Fetched items:', items);
},
(error) => {
console.error('Error fetching items:', error);
// error will be an HttpErrorResponse object with status 404
}
);
// Expected Output (in the console):
// Error fetching items: HttpErrorResponse { ... status: 404, ... }
Constraints
- Angular Version: Use a recent stable version of Angular (e.g., Angular 14+).
- TypeScript Version: Utilize modern TypeScript features.
- API Endpoint: For the purpose of this challenge, you can use a publicly available mock API (like JSONPlaceholder) or simulate one using a simple HTTP server setup if you have one. For simplicity, assume the endpoint
https://jsonplaceholder.typicode.com/usersis available and can be used to fetch user data. You'll adapt theIteminterface to match the user data structure. - Error Object: When handling errors, the service should return an observable that emits an error object compatible with
HttpErrorResponse.
Notes
- Consider using RxJS operators like
catchErrorto gracefully handle errors within your service. - Think about how you will structure the error handling to be informative for the component that consumes your service.
- Define an interface for the data you expect to receive from the API. This will greatly improve code readability and maintainability.
- Make sure your service is registered as a provider, typically at the
rootlevel, for global accessibility.