MyHttpResource is a wrapper around Angular’s HttpClient that provides a reactive approach to working with HTTP requests.
It automatically manages states (loading, error, value), processes URL parameters, and allows easy configuration of post-request handling.
HttpClient, so don’t forget to add a call to the provideHttpClient() function in the project configuration under the providers field.
- You create a “resource” once (e.g., posts, sendPost, updatePost, etc.) with all its settings.
- A resource always includes:
loading: Signal<boolean>value: WritableSignal<T>error: WritableSignal<unknown>fetch(fetchData?)— executes the request immediately and updates signals.request$(fetchData?)— returns anObservable<T>without side effects.
If manual: false (default), the request is executed immediately when the resource is created. If manual: true, it only runs when you call fetch() or request$().
To install, run npm install my-http-resource or yarn add my-http-resource and import the required entities into your application:
import { IHttpResource, myHttpResource, Get, Post, Put, Patch, Delete } from 'my-http-resource';public getRequestData: IHttpResource<Get, TData> = myHttpResource().get<TData>({
url:'your_url/{{myId}}',
pipe: pipe(delay(1000)),
afterSuccess: (data: TData) => this.afterSuccess(data),
afterError: (error: HttpErrorResponse) => this.afterError(error),
urlParams: { myId: 2 },
headers: {
testHeader: '12345'
},
queryParams: { currency: 'USD' },
manual: true,
initialValue: [],
mergeValues: false,
opttions: {
// httpOptions for get request
}
});
public myMethod(): void {
this.getRequestData.fetch({
queryParams: { currency: 'EUR' },
urlParams: { myId: 3 },
mergeValues: true,
opttions: {
// httpOptions for get request
}
})
}public postRequestData: IHttpResource<Post, TData> = myHttpResource().post<TData>({
url:'your_url/{{myId}}',
manual: true,
body: { name: 'Elizabeth'},
pipe: pipe(delay(1000)),
afterSuccess: (data: TData) => this.afterSuccess(data),
afterError: (error: HttpErrorResponse) => this.afterError(error),
urlParams: { myId: 2 },
headers: {
testHeader: '12345'
},
manual: true,
initialValue: [],
mergeValues: false,
opttions: {
// httpOptions for post request
}
});
public myMethod(): void {
this.postRequestData.fetch({
body: { name: 'Arnold'},
urlParams: { myId: 3 },
mergeValues: true,
opttions: {
// httpOptions for post request
}
})
}public putRequestData: IHttpResource<Put, TData> = myHttpResource().put<TData>({
url:'your_url/{{myId}}',
manual: true,
body: { name: 'Elizabeth'},
pipe: pipe(delay(1000)),
afterSuccess: (data: TData) => this.afterSuccess(data),
afterError: (error: HttpErrorResponse) => this.afterError(error),
urlParams: { myId: 2 },
headers: {
testHeader: '12345'
},
manual: true,
initialValue: [],
mergeValues: false,
opttions: {
// httpOptions for put request
}
});
public myMethod(): void {
this.putRequestData.fetch({
body: { name: 'Arnold'},
urlParams: { myId: 3 },
mergeValues: true,
opttions: {
// httpOptions for put request
}
})
}public patchRequestData: IHttpResource<Patch, TData> = myHttpResource().patch<TData>({
url:'your_url/{{myId}}',
manual: true,
body: { name: 'Elizabeth'},
pipe: pipe(delay(1000)),
afterSuccess: (data: TData) => this.afterSuccess(data),
afterError: (error: HttpErrorResponse) => this.afterError(error),
urlParams: { myId: 2 },
headers: {
testHeader: '12345'
},
manual: true,
initialValue: [],
mergeValues: false,
opttions: {
// httpOptions for patch request
}
});
public myMethod(): void {
this.putRequestData.fetch({
body: { name: 'Arnold'},
urlParams: { myId: 3 },
mergeValues: true,
opttions: {
// httpOptions for patch request
}
})
}public deleteRequestData: IHttpResource<Delete, TData> = myHttpResource().delete<TData>({
url:'your_url/{{myId}}',
pipe: pipe(delay(1000)),
afterSuccess: (data: TData) => this.afterSuccess(data),
afterError: (error: HttpErrorResponse) => this.afterError(error),
urlParams: { postId: 2 },
headers: {
testHeader: '12345'
},
queryParams: { currency: 'USD' },
manual: true,
initialValue: [],
mergeValues: false,
body: { name: 'Elizabeth' },
opttions: {
// httpOptions for delete request
}
});
public myMethod(): void {
this.deleteRequestData.fetch({
body: { name: 'Arnold'}, // if
queryParams: { currency: 'EUR' },
urlParams: { myId: 3 },
mergeValues: true,
opttions: {
// httpOptions for get request
}
})
}// app.service.ts
import { Injectable, inject } from '@angular/core';
import { myHttpResource } from 'my-http-resource';
export interface IPost {
postId: number;
id: number;
name: string;
email: string;
body: string;
}
@Injectable()
export class AppService {
// GET: /posts/{{postId}}?limit=... - All parameters except url are optional.
public posts = myHttpResource().get<IPost[]>({
url: '/api/posts/{{postId}}',
urlParams: { postId: 1 },
queryParams: { limit: 10 },
initialValue: [], // What to populate value with before the response.
afterSuccess: (data: IPost[]) => console.log('got posts', data),
afterError: (e) => console.warn('get error', e),
// manual: true, // If you need to disable auto-start.
mergeValues: true, // If you want the received data to be merged with the previous ones instead of overwriting them.
});
// POST: /posts - All parameters except url are optional.
public sendPost = myHttpResource().post<IPost>({
url: '/api/posts',
body: { name: 'John', email: 'john@mail.com', body: 'Hello' },
headers: { 'X-Trace-Id': 'abc-123' },
manual: true, // Send manually via fetch().
mergeValues: true
});
// PUT: /posts/{{id}} - All parameters except url are optional.
public updatePost = myHttpResource().put<IPost>({
url: '/api/posts/{{id}}',
urlParams: { id: 1 },
manual: true,
});
// PATCH: /posts/{{id}} - All parameters except url are optional.
public patchPost = myHttpResource().patch<IPost>({
url: '/api/posts/{{id}}',
urlParams: { id: 1 },
manual: true,
});
// DELETE: /posts/{{id}} - All parameters except url are optional.
public deletePost = myHttpResource().delete<void>({
url: '/api/posts/{{id}}',
urlParams: { id: 1 },
manual: true,
});
}// app.component.ts (usage snippets)
import { Component, inject } from '@angular/core';
import { AppService, IPost } from './services/app.service';
import { forkJoin } from 'rxjs';
@Component({
selector: 'app-root',
template: `
<button (click)="fetchPost()">Load posts</button>
<button (click)="sendPostHandler()">Create post</button>
<button (click)="putPostHandler()">PUT</button>
<button (click)="patchPostHandler()">PATCH</button>
<button (click)="deletePostHandler()">DELETE</button>
<button (click)="requestObservable()">forkJoin example</button>
@if (appService.posts.loading()) {
<div>Loading...</div>
}
@if (appService.posts.error()) {
<pre">Error: {{ err | json }}</pre>
}
<pre>{{ appService.posts.value() | json }}</pre>
`,
})
export class AppComponent {
protected appService = inject(AppService);
fetchPost(): void {
this.appService.posts.fetch(); // GET
}
sendPostHandler(): void {
this.appService.sendPost.fetch(); // POST
}
putPostHandler(): void {
this.appService.updatePost.fetch({
body: { name: 'New', email: 'n@e.com', body: '...' },
mergeValues: true,
});
}
patchPostHandler(): void {
this.appService.patchPost.fetch({ body: { name: 'Patch only name' }});
}
deletePostHandler(): void {
this.appService.deletePost.fetch();
}
requestObservable(): void {
forkJoin({
one: this.appService.posts.request$({ urlParams: { postId: 5 } }),
two: this.appService.sendPost.request$(),
}).subscribe(({ one, two }) => {
// Manually update the signal if needed.
this.appService.posts.value.set(one);
});
}
}Use placeholders {{...}} in the url. They are filled from urlParams.
url: '/api/users/{{ userId }}/posts/{{ postId }}',
urlParams: { userId: 42, postId: 7 } // → /api/users/42/posts/7If a key is missing, you’ll get a clear error: “Missing value for URL parameter…”.
Set them directly in the config or when calling fetch()/request$().
public data = this.http.get<IData>({
url: '/api/posts/{{postId}}',
queryParams: { limit: 20, search: 'angular' },
urlParams: { postId: 1 }
});Override at call time:
data.fetch({
queryParams: { limit: 5 },
urlParams: { postId: 1 }
});Define it in the config or override when calling:
public data = this.http.post<IData>({
url: '/api/posts',
body: { title: 'Hello', body: 'World' },
});You can override it in fetch() or request$().
data.fetch({ body: { title: 'Updated' } });- By default, requests are executed immediately.
- Use
manual: trueto control execution manually.
public data = myHttpResource().post<IData>({
url: '/api/items',
manual: true, // Doesn’t start automatically
});
// later:
data.fetch();public data = myHttpResource().post<IData>({
url: '/api/items',
afterSuccess: (data) => console.log('OK', data),
afterError: (err) => console.warn('ERR', err),
});These callbacks are invoked automatically when you call fetch() or when the request is triggered automatically with manual = false. For request$() (a plain Observable), the callbacks won’t fire—you decide when to subscribe and what to do with the result.
You can pipe RxJS operators into the request stream. You can’t override it in fetch() or request().
import { map } from 'rxjs/operators';
public data = myHttpResource().get<{ id: number; name: string }[]>({
url: '/api/users',
pipe: pipe(
map(list => list.filter(u => !!u.name))
),
});- If you need to merge the data from the previous request with the current one, use the flag
mergeValues: true. - The data must be of the same type — either an array or an object. If the data types do not match, the
mergeValuesflag will not work.
public data = myHttpResource().post<IData>({
url: '/api/items',
manual: true,
initialValue: [],
mergeValues: true,
});
data.fetch({
body: { title: 'Updated' },
mergeValues: false
});- Automatic request when
manual = false. This does not prevent you from later callingfetch()orrequest(). fetch(fetchData?)— makes a request, automatically setsloading = true, puts the result intovalue, and the error intoerror.request$(fetchData?)— returns anObservable<T>without side effects. Handy for compositions (forkJoin, switchMap, etc.). The state in signals does not change unless you update it yourself.
Every resource provides:
loading()— true / false.value()— current value (you can set/update).error()— HttpErrorResponse | unknown (you can set/update).
Example of manually updating the value:
resource.value.update(prev => [...prev, newItem]);Get/Delete → { queryParams?, urlParams? }Post/Patch/Put → { body?, urlParams? }IHttpResource<Method>:loading: Signal<boolean>value: WritableSignal<any>error: WritableSignal<unknown>fetch(fetData?: Method): voidrequest$(fetData?: Method): Observable<any>
The generic T in get() / post() / ... is the type of the expected response.