skip to content
Alvin Lucillo

Async operations with resource

/ 2 min read

You can use resource to make asynchronous calls more declarative and succinct rather than handling the operation yourself. For example, we have this component that queries posts from the API. When component is created, the API is queried and the template renders the posts. It’s plain code that uses signal to hold posts.

Nothing out of ordinary. When user enters a value, the posts are reloaded when the event handler is called with the value of input field.

export class PostsComponent {
	constructor() {
		this.loadPosts("");
	}

	posts = signal<Post[]>([]);

	onSearch(value?: string) {
		this.loadPosts(value ?? "");
	}

	async loadPosts(q: string) {
		const resp = await fetch(`https://dummyjson.com/posts/search?q=${q}`);
		const json = await resp.json();

		this.posts.set(json.posts);
	}
}

interface Post {
	id: number;
	body: string;
	title: string;
	userId: number;
}
Search:
<input #input (keydown)="onSearch(input.value)" />

Posts:
<table>
	@for(post of posts(); track post.id){
	<tr>
		<td>{{ post.id }}</td>
		<td>{{ post.title }}</td>
		<td>{{ post.body }}</td>
	</tr>

	}
</table>

Now, using resource, notice that we’re not using posts as a signal of array of posts and calling the fetch when a value is entered. The query happens when the search value changes in request. request triggers the query which is when the signal’s value is changed. loader performs API call and uses the query parameter from the request.

It’s a bit more declarative now in a way that we don’t call the API ourselves when a value is entered, which we can consider as a synchronous process. In contrast, when we use signal-based operation to determine when we should perform the API call, we’re relying on Angular’s change detection system. We’re indirectly relying on resource’s ability to detect changes, which we can consider as an asynchronous process.

export class PostsComponent {
	query = signal("");

	posts = resource<
		Post[],
		{
			query: string;
		}
	>({
		request: () => ({
			query: this.query(),
		}),
		loader: async ({ request }) => {
			const response = await fetch(`https://dummyjson.com/posts/search?q=${request.query}`);
			const json = await response.json();
			return json.posts;
		},
	});

	onSearch(value: string) {
		this.query.set(value);
	}
}

interface Post {
	id: number;
	body: string;
	title: string;
	userId: number;
}
Search:
<input #input (keydown)="onSearch(input.value)" />

Posts:
<table>
	<!-- we're now using posts.value() -->
	@for(post of posts.value(); track post.id){
	<tr>
		<td>{{ post.id }}</td>
		<td>{{ post.title }}</td>
		<td>{{ post.body }}</td>
	</tr>

	}
</table>