npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ngx-http-request-state

v3.5.0

Published

Angular library for wrapping HttpClient responses with loading & error information to allow observing load/loading/error state changes as a stream of events.

Downloads

2,725

Readme

ngx-http-request-state

An Angular library for wrapping HttpClient responses with loading & error information.

Allows observing the whole lifecycle of HTTP requests as a single observable stream of state changes, simplifying handling of loading, loaded & error states.

If you have found this library useful, please consider donating to say thanks and support its development:

Versions

Version ^3.5.0 supports Angular 14 - 19. Version ^3.2.0 supports Angular 14 - 18. Version ^3.1.0 supports Angular 14 - 17. Version 3.0.0 supports Angular 14, 15 & 16. Version ^2.1.0 supports Angular 14 & 15. Version 1.2.0 supports Angular 8 to 13. Angular versions 7 and earlier are not supported.

API

The library declares an HttpRequestState interface to reflect the state of an HTTP request:

export interface HttpRequestState<T> {
  /**
   * Whether a request is currently in-flight.  true for a "loading" state,
   * false otherwise.
   */
  readonly isLoading: boolean;
  /**
   * The response data for a "loaded" state, or optionally the last-known data
   * (if any) for a "loading" or "error" state.
   */
  readonly value?: T;
  /**
   * The response error (if any) for an "error" state.
   */
  readonly error?: HttpErrorResponse | Error;
}

There are three subtypes of HttpRequestStatus provided with more tightly-defined members, as well as a set of type-guard predicates:

export interface LoadingState<T> extends HttpRequestState<T> {
  readonly isLoading: true;
  readonly value?: T;
  readonly error: undefined;
}

export interface LoadedState<T> extends HttpRequestState<T> {
  readonly isLoading: false;
  readonly value: T;
  readonly error: undefined;
}

export interface ErrorState<T> extends HttpRequestState<T> {
  readonly isLoading: false;
  readonly value?: T;
  readonly error: HttpErrorResponse | Error;
}

export declare function isLoadingState<T>(state?: HttpRequestState<T>): state is LoadingState<T>;
export declare function isLoadedState<T>(state?: HttpRequestState<T>): state is LoadedState<T>;
export declare function isErrorState<T>(state?: HttpRequestState<T>): state is ErrorState<T>;

Finally, a function called httpRequestStates() is provided which returns an RxJs operator that transforms an Observable<HttpResponse<T>> into an Observable<HttpRequestState<T>>:

export class SomeComponent {
  /**
   * Will immediately emit a LoadingState, then either a LoadedState<MyData> or
   * an ErrorState, depending on whether the underlying HTTP request was successful.
   */
  readonly myData$: Observable<HttpRequestState<MyData>> = this.httpClient
    .get<MyData>(someUrl)
    .pipe(httpRequestStates());

  constructor(private httpClient: HttpClient) {}
}

The associated HTML template can then async-pipe this state to display either a loading spinner, the data, or an error state:

<ng-container *ngIf="myData$ | async as myData">
  <!-- Show a spinner if state is loading -->
  <my-loading-spinner *ngIf="myData.isLoading"></my-loading-spinner>

  <!-- Show the data if state is loaded -->
  <my-data-view *ngIf="myData.value" [myData]="myData.value"></my-data-view>

  <!-- Show an error message if state is error -->
  <my-error-state *ngIf="myData.error" [error]="myData.error"></my-error-state>
</ng-container>

switchMap safety

The httpRequestStates() operator catches errors and replaces them with ordinary (next) emission of an ErrorState object instead, so it will never throw an error.

This means when used inside a switchMap, no special error handling is required to prevent the outer observable being unsubscribed following an error response in the inner observable:

export class SomeComponent {
  /**
   * Every time the source observable (activatedRoute.params) emits a value,
   * this observable will immediately emit a LoadingState followed later by
   * either a LoadedState<MyData> or an ErrorState.
   *
   * It will continue to emit new HttpRequestStates following values being emitted
   * from the source observable, even if errors were thrown by the http client
   * for earlier requests.
   */
  readonly myData$: Observable<HttpRequestState<MyData>> = this.activatedRoute.params.pipe(
    pluck('id'),
    distinctUntilChanged(),
    // The .pipe(httpRequestStates()) needs to be _inside_ the switchMap so that it can
    // catch errors throw by the inner observable.  If httpRequestStates() was just
    // placed after the switchMap operator in the outer pipe instead, then an error from
    // the inner observable (httpClient.get) would make the swichMap operator unsubcribe
    // from the outer observable, and so we'd no longer react to changes in the route params.
    switchMap((id) => this.httpClient.get<MyData>(`${baseUrl}?id=${id}`).pipe(httpRequestStates()))
  );

  constructor(private httpClient: HttpClient, private activatedRoute: ActivatedRoute) {}
}

Merging

The intention of this library is to provide a view model loading state, where you prep all the data first then, pipe it through httpRequestStates towards the end, instead of piping it at the lowest level, for instance in an API service.

An example of this can be seen in the examples app.

If you however already have multiple HttpRequestState<T> objects and would like to merge the values together, then mergeStates can be used.

Consider the following example where we assume we have no control over MyDataService and it already wraps requests in HttpRequestState:

// Third party
@Injectable()
export class MyDataService {
  constructor(private httpClient: HttpClient) {}

  getMyData(someParameter: any) {
    return this.httpClient.get<MyData>(someUrl + someParameter).pipe(httpRequestStates());
  }
}

// Our component
export class SomeComponent {
  readonly myDataCollection$ = combineLatest([
    this.myDataService.getMyData('red'),
    this.myDataService.getMyData('blue'),
  ]).pipe(
    map((states) =>
      mergeStates(states, (dataArray) => {
        // Merge list of data together then return a new instance of MyData
      })
    )
  );

  constructor(private myDataService: MyDataService) {}
}

(We use combineLatest instead of forkJoin to get loading updates)

Using mergeStates allows you to act "inside" the HttpRequestState, directly on the values or the errors.

As long as one of the states are loading, the resulting merged state will be a LoadingState. When all finish successfully, the callback of the second argument is called with all the available values.

If an error occurs in any of the requests, the merged state will be an ErrorState. By default the first of the errors will be returned. It is possible to override this with the third argument.

Example:

export class SomeComponent {
  readonly myDataCollection$ = combineLatest([
    this.myDataService.getMyData('will-fail'),
    this.myDataService.getMyData('will-also-fail'),
    this.myDataService.getMyData('blue'),
  ]).pipe(
    map((states) =>
      mergeStates(
        states,
        (dataArray) => {
          // Merge list of data together then return a new instance of MyData
        },
        (errors) => {
          // Combine the errors and return a new instance of HttpErrorResponse or Error
        }
      )
    )
  );

  constructor(private myDataService: MyDataService) {}
}

Examples

See the examples app for more example use cases.