← All articles

Rewriting HttpClient Requests with httpResource

How httpResource replaces HttpClient service calls with Signal-driven requests, including reload(), and an Ionic pattern with ViewDidEnter.

Published
Rewriting HttpClient Requests with httpResource cover image

httpResource is convenient. It is Signal-based and re-runs the request whenever Signals used inside the request change. Personally, I like Resource.reload(). Most Angular users probably routed HTTP through services with code like this:

@Injectable({...})
export class ExampleService {
  readonly #http = inject(HttpClient);
  
  // When handling the request with a Promise
  getDataPromise(id: string): Promise<Data> {
    return firstValueFrom(this.#http.get<Data>(environment.api + 'example/' + id));
  }

  // Observable-based
  getDataObservable(id: string): Observable<Data> {
    return this.#http.get<Data>(environment.api + 'example/' + id);
  }
}

Whether I reflected that in the template with the async pipe or assigned it to a component property, it was familiar code for a long time. Now I can write it like this:

@Injectable({...})
export class ExampleService {
  readonly #http = inject(HttpClient);
  
  // When handling the request with a Promise
  getData(id: Signal<string>): HttpResourceRef<Data> {
    return httpResource<Data>(environment.api + 'example/' + id());
  }
}

I bind it directly on the component:

@Component({...})
export class ExamplePage {
  readonly id = input.required<number>();
  readonly #exampleService = inject(ExampleService);
  readonly dataResource = this.#exampleService.getData(this.id);
}

The main thing to watch is passing the Signal itself as an argument, not unwrapping its value. Requests are handled by Signal values without lifecycle hooks—simple and very nice. I recommend trying it.

With Ionic Angular, the code looks like this (for the ViewDidEnter property, see https://zenn.dev/rdlabo/articles/bb03d724d33831):

@Component({...})
export class ExamplePage {
  readonly id = input.required<number>();
  readonly el = inject(ElementRef);
  readonly #didEnter = toSignal(createDidEnter(this.el));
  readonly #exampleService = inject(ExampleService);
  readonly dataResource = this.#exampleService.getData(this.#didEnter, this.id);
}
@Injectable({...})
export class ExampleService {
  readonly #http = inject(HttpClient);
  
  getData(didEnter: Signal<boolean>, id: Signal<string>): HttpResourceRef<Data> {
    return httpResource<IMovie[]>(() => {
      if (!didEnter()) {
        return undefined;
      }
      return environment.api + 'example/' + id();
    });
  }
}

Compared to before, I can write this much more simply. The API is still experimental, but give it a try!