// ==================================================
// 1. Basic resource() Function Usage
// ==================================================
import { Component, signal, resource } from '@angular/core';
import { HttpClient } from '@angular/common/http';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-profile',
template: `
<div>
<h2>User Profile</h2>
<!-- Show loading state -->
@if (userResource.isLoading()) {
<p>Loading user data...</p>
}
<!-- Show error state -->
@if (userResource.error()) {
<p class="error">Error: {{ userResource.error()?.message }}</p>
}
<!-- Show data -->
@if (userResource.value()) {
<div class="user-card">
<h3>{{ userResource.value()?.name }}</h3>
<p>Email: {{ userResource.value()?.email }}</p>
<p>ID: {{ userResource.value()?.id }}</p>
</div>
}
<button (click)="loadUser(2)">Load User 2</button>
<button (click)="loadUser(3)">Load User 3</button>
</div>
`
})
export class UserProfileComponent {
private http = inject(HttpClient);
// Signal to track which user ID to load
userId = signal(1);
// Create a resource that depends on userId signal
userResource = resource({
request: () => ({ id: this.userId() }),
loader: async ({ request }) => {
// Simulate API call with Promise
const response = await fetch(`/api/users/${request.id}`);
return response.json() as Promise<User>;
}
});
loadUser(id: number) {
this.userId.set(id); // This will trigger the resource to reload
}
}
// ==================================================
// 2. Resource API Interface Usage
// ==================================================
import { Resource, ResourceStatus } from '@angular/core';
@Component({
selector: 'app-data-manager',
template: `
<div>
<h2>Data Manager</h2>
<!-- Generic resource display -->
<div class="resource-info">
<p>Status: {{ getStatusText(dataResource.status()) }}</p>
<p>Is Loading: {{ dataResource.isLoading() }}</p>
<p>Has Error: {{ !!dataResource.error() }}</p>
</div>
@if (dataResource.value()) {
<pre>{{ dataResource.value() | json }}</pre>
}
<button (click)="refreshData()">Refresh Data</button>
</div>
`
})
export class DataManagerComponent {
private http = inject(HttpClient);
// Using Resource API interface for type safety
dataResource: Resource<any> = resource({
loader: async () => {
// Simulate async operation
await new Promise(resolve => setTimeout(resolve, 1000));
return {
timestamp: new Date().toISOString(),
data: 'Sample data loaded!'
};
}
});
// Helper method to work with Resource API
getStatusText(status: ResourceStatus): string {
switch (status) {
case ResourceStatus.Idle: return 'Idle';
case ResourceStatus.Loading: return 'Loading';
case ResourceStatus.Resolved: return 'Resolved';
case ResourceStatus.Error: return 'Error';
case ResourceStatus.Reloading: return 'Reloading';
default: return 'Unknown';
}
}
refreshData() {
this.dataResource.reload();
}
}
// ==================================================
// 3. Advanced resource() with Dependencies
// ==================================================
@Component({
selector: 'app-posts-list',
template: `
<div>
<h2>Posts by Category</h2>
<select (change)="onCategoryChange($event)">
<option value="">All Categories</option>
<option value="tech">Technology</option>
<option value="news">News</option>
<option value="sports">Sports</option>
</select>
@if (postsResource.isLoading()) {
<div class="loading">Loading posts...</div>
}
@if (postsResource.error()) {
<div class="error">
Failed to load posts: {{ postsResource.error()?.message }}
<button (click)="postsResource.reload()">Retry</button>
</div>
}
@if (postsResource.value()) {
<div class="posts-grid">
@for (post of postsResource.value(); track post.id) {
<div class="post-card">
<h3>{{ post.title }}</h3>
<p>{{ post.excerpt }}</p>
<small>Category: {{ post.category }}</small>
</div>
}
</div>
}
</div>
`
})
export class PostsListComponent {
private http = inject(HttpClient);
// Signals for filtering
selectedCategory = signal<string>('');
searchTerm = signal<string>('');
// Resource with multiple dependencies
postsResource = resource({
request: () => ({
category: this.selectedCategory(),
search: this.searchTerm()
}),
loader: async ({ request, abortSignal }) => {
// Build query parameters
const params = new URLSearchParams();
if (request.category) params.set('category', request.category);
if (request.search) params.set('search', request.search);
// Use abort signal for cancellation
const response = await fetch(`/api/posts?${params}`, {
signal: abortSignal
});
if (!response.ok) {
throw new Error(`Failed to fetch posts: ${response.statusText}`);
}
return response.json();
}
});
onCategoryChange(event: Event) {
const target = event.target as HTMLSelectElement;
this.selectedCategory.set(target.value);
}
}
// ==================================================
// 4. Using rxResource() with Observables
// ==================================================
import { rxResource } from '@angular/core';
import { switchMap, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
@Component({
selector: 'app-reactive-data',
template: `
<div>
<h2>Reactive Data with rxResource</h2>
<input
#searchInput
(input)="searchQuery.set(searchInput.value)"
placeholder="Search..."
/>
@if (searchResource.isLoading()) {
<div>Searching...</div>
}
@if (searchResource.value()) {
<div class="results">
@for (item of searchResource.value(); track item.id) {
<div class="result-item">{{ item.title }}</div>
}
</div>
}
</div>
`
})
export class ReactiveDataComponent {
private http = inject(HttpClient);
searchQuery = signal('');
// Using rxResource with Observable
searchResource = rxResource({
request: () => ({ query: this.searchQuery() }),
loader: ({ request }) => {
if (!request.query) {
return of([]);
}
return this.http.get<any[]>(`/api/search?q=${request.query}`).pipe(
catchError(error => {
console.error('Search error:', error);
return of([]);
})
);
}
});
}
// ==================================================
// 5. Resource API Type Guards and Utilities
// ==================================================
import { ResourceRef } from '@angular/core';
// Utility functions for working with Resource API
export class ResourceUtils {
// Type guard to check if resource has data
static hasData<T>(resource: Resource<T>): resource is Resource<T> & { value(): T } {
return resource.status() === ResourceStatus.Resolved && resource.value() !== undefined;
}
// Type guard to check if resource is in error state
static hasError<T>(resource: Resource<T>): resource is Resource<T> & { error(): Error } {
return resource.status() === ResourceStatus.Error && resource.error() !== undefined;
}
// Generic resource state handler
static handleResourceState<T>(
resource: Resource<T>,
handlers: {
loading?: () => void;
error?: (error: Error) => void;
success?: (data: T) => void;
}
) {
if (resource.isLoading() && handlers.loading) {
handlers.loading();
} else if (this.hasError(resource) && handlers.error) {
handlers.error(resource.error());
} else if (this.hasData(resource) && handlers.success) {
handlers.success(resource.value());
}
}
}
// ==================================================
// 6. Custom Resource Hook Pattern
// ==================================================
// Custom composable for user data
export function useUserData(userId: Signal<number>) {
const http = inject(HttpClient);
return resource({
request: () => ({ id: userId() }),
loader: async ({ request }) => {
const response = await fetch(`/api/users/${request.id}`);
if (!response.ok) {
throw new Error(`User not found: ${response.status}`);
}
return response.json() as Promise<User>;
}
});
}
// Usage in component
@Component({
selector: 'app-user-details',
template: `
<div>
@if (userResource.isLoading()) {
<p>Loading user...</p>
}
@if (userResource.error()) {
<p>Error: {{ userResource.error()?.message }}</p>
}
@if (userResource.value()) {
<h2>{{ userResource.value()?.name }}</h2>
}
</div>
`
})
export class UserDetailsComponent {
userId = signal(1);
userResource = useUserData(this.userId);
}