Goal: Connect your application to a backend using the new httpResource API. You will learn to fetch data declaratively via a Service, handle loading/error states, and manage mutations using Observables.
Estimated Time: 70 Minutes
Ensure your backend is running.
- Check your terminal where
npm run devis active. - Verify
http://localhost:3000/eventsreturns a JSON array in your browser.
We will define our data fetching logic inside a service using the experimental httpResource API, which simplifies HTTP GET requests into Signals.
- Create a new file:
src/app/core/events.service.ts. - Define the class. We need a method to create the resource (factory pattern) and a method for deletion.
// src/app/core/events.service.ts
import { Injectable, inject, Signal } from '@angular/core';
import { HttpClient, httpResource } from '@angular/common/http';
import { Observable } from 'rxjs';
import { DevFestEvent } from '../models/event.model';
@Injectable({
providedIn: 'root',
})
export class EventsService {
private http = inject(HttpClient);
private apiUrl = 'http://localhost:3000/events';
// 1. Define the Resource Factory
// We accept a Signal<string> so the resource can react to changes automatically.
getEventsResource(query: Signal<string>) {
return httpResource<DevFestEvent[]>(() => {
const q = query();
// The function returns the URL to fetch.
// Whenever 'q' changes, httpResource re-fetches automatically.
return q ? `${this.apiUrl}?q=${q}` : this.apiUrl;
});
}
// 2. Delete an event
// We return an Observable (classic pattern) for the component to subscribe to.
deleteEvent(id: string): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}Now we use this service in our Component. Notice how much cleaner this is compared to the old ngOnInit style.
- Open
src/app/features/events/event-list.ts. - Import
inject. - Inject the service and initialize the resource.
// src/app/features/events/event-list.ts
import { Component, inject, signal } from '@angular/core';
import { EventsService } from '../../core/events.service';
// ... imports (EventCard, SearchBar)
@Component({
// ... selector/template/imports
})
export class EventList {
private eventsService = inject(EventsService);
// Existing state from Mod 1
searchQuery = signal('');
// 1. Initialize the Resource
// We pass our signal directly to the service.
// This creates a live connection: searchQuery -> URL -> HTTP Request -> events.value
readonly events = this.eventsService.getEventsResource(this.searchQuery);
}The httpResource object gives us standard signals:
.isLoading(): Boolean.error(): Error object.hasValue(): Boolean (Safe check).value(): The data (Accessor)
Critical Safety Note: We must check events.hasValue() before accessing events.value(), otherwise Angular will throw an error if the data isn't ready.
- Update the template in
event-list.tsto handle these states.
<!-- src/app/features/events/event-list.ts -->
<!-- 1. Error State -->
@if (events.error()) {
<div class="bg-red-100 text-red-700 p-4 rounded-lg mb-6">
Failed to load events. Is the server running?
</div>
}
<!-- 2. Loading State -->
@if (events.isLoading()) {
<div class="text-center py-12 text-gray-500 animate-pulse">Loading events...</div>
}
<!-- 3. Data State -->
<!-- We guard the value access with hasValue() -->
@if (events.hasValue()) {
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@for (event of events.value(); track event.id) {
<app-event-card
[title]="event.title"
[date]="event.date"
[image]="event.image"
(delete)="deleteEvent(event.id)"
/>
} @empty {
<p class="col-span-3 text-center text-gray-500">No events found.</p>
}
</div>
}Because httpResource inside the service is observing the searchQuery signal we passed in:
- Go to your browser.
- Type "Angular" in the Search Bar.
- Observe: The list updates automatically.
To delete an item, we will use the classic Observable subscription pattern for the action, and then refresh the resource.
- In
event-list.ts, implement thedeleteEventmethod.
export class EventList {
// ... previous code ...
deleteEvent(id: string) {
this.eventsService.deleteEvent(id).subscribe(() => {
this.events.reload();
});
}
}- Verify Event Binding:
Ensure your
@forloop in the template is calling this method:(delete)="deleteEvent(event.id)".
Let's simulate a server crash.
- Go to the terminal running
npm run server. - Press
Ctrl + Cto stopjson-server. - Reload the browser.
- Observe: You should see the red "Failed to load events" banner.
- Restart Server: Run
npm run serveragain. - Click "Reload" (or refresh page) to see data return.
You have successfully implemented Declarative Data Fetching with httpResource.
Key Takeaways:
httpResource: A specialized primitive for HTTP GET requests.- Safety First: Always use
.hasValue()before accessing the data signal. - Hybrid Patterns: Using Signals for Reading data (Resource) and Observables for Writing data (deleteEvent) is the standard pattern in Angular.
Next Module: We will build the Event Details page using modern Routing and Component Input Binding.