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-data/query

v0.0.2

Published

> Asynchronous state management solution for Angular 15, built on RxJS and inspired by Tanstack's `react-query`.

Downloads

6

Readme

Ngx-data

Asynchronous state management solution for Angular 15, built on RxJS and inspired by Tanstack's react-query.

The goal

Interacting with data sources asynchronously is a key activity for Angular and Angular universal applications. While Angular comes with an amazing defacto solutions (RxJS) for fetching data and building declarative state management capabilities they also bring steep learning curves to new comers due to its declarative, push centric design paradigm and its huge collection of powerful yet quirky operators. Even as a seasoned user of RxJS, the need to copy/paste declarative patterns to new features and deal with differences between legacy patterns and shiny new ones I just came up with made me groan. This is why I made @ngx-data, a collection of utilities and injectables that will hopefully make working with data easier for you and me in the future.

Thank you so much for checking out this library, if you have any suggestions and comments feel free to open an issue I will respond as soon as I can :)

The design

After looking at serveral asynchronous state management solutions and strategies, the following design principles are solidified:

  1. The client must be easy to adopt.
  2. The client should be used in the fascade/service layer.
  3. The client should offer necesary capabilities even if there are performance tradeoffs.

Installation and your first query

To add this package to your project, install it with the package manager of your choice.

npm install @ngx-data/query
yarn @ngx-data/query

Then import the query client module (NgxDataQueryModule) in the app.module.ts, this will provide the query client to the rest of your application.

@NgModule({
  imports: [BrowserModule, HttpClientModule, NgxDataQueryModule.forRoot()],
  declarations: [AppComponent],
  bootstrap: [AppComponent],
})
export class AppModule {}

Now in your service/component inject the query client and start using it:

const USERS_URL = '/api/users';
@Injectable()
export class UserService {
  constructor(private client: QueryClient, private http: HttpClient) {}

  listUsers(): Observable<User[]> {
    return this.client.query(USERS_URL, () => this.http.get<User[]>(USERS_URL));
  }
}

That's it! Your listUsers method now has caching enabled!

Mutating data

When you change the data - add a new record, update an existing one, or delete one; you want the data to be refetched, you can use the invalidate feature to invalidate the cached data and trigger a refetch.

const USERS_URL = '/api/users';
@Injectable()
export class UserService {
  constructor(private client: QueryClient, private http: HttpClient) {}

  listUsers(): Observable<User[]> {
    return this.client.query(USERS_URL, () => this.http.get<User[]>(USERS_URL));
  }

  updateUser(id: number, data: User) {
    return this.http
      .put(`${USERS_URL}/${id}`, data)
      .pipe(tap(() => this.client.invalidate(USERS_URL)));
  }
}

Recreating a query

To recreate a query if it already exists, use the forceRecreate option when calling .query.

this.client.query(['todo'], {
  forceRecreate: true,
});

When you recreate a query the existing query will be discharged emiting an EMPTY complete response to all subscriptions and completing the original observable. All original subscribers will no longer have access to data updates. Be careful when you recreate queries.

Setting automatic retries

The query client will retry your observable if it produces errors, you may change this behavior on a per request basis or globally by setting the retries option. Default value is 3 times

this.client.query(['todo'], {
  retries: 0, // Do not retry
});

this.client.query(['todo'], {
  retries: Infinity, // Never stop retrying
});

Setting a query expiry time

The query client can automatically refetch data periodically using cache expiration time. Set it using the query config. The unit is miliseconds and the default value is Infinity.

this.client.query(['todo'], {
  expiresIn: 60_000, // Refetch the data automatically every minute
});

When a request expires it will automatically refetch the data from the provided observable.

Observing request status

You can observe the status on an observable using the query client's getQueryStatus method. You can use the query status to determine the state of the query. The available states are:

  • idle - when a query has been created but no data has been cached or fetched
  • loading - when a query's observable upstream is being resolved
  • success - when a query has fetched data successfully and has the data cached in memory
  • error - when a query failed all retry attempts, and an error was thrown every time
  • stale - when a query has passed its stalesIn timer.

To get an observable stream of the request state, do:

class Component {
  status$ = this.client.getQueryStatus(['todo', { done: true }]);
}

Then in your template

<ng-container *ngIf="status$ as status">
  <div [ngSwitch]="status" *ngIf="status !== success">
          
    <div *ngSwitchCase="'loading'">Loading</div>
          
    <div *ngSwitchCase="'stale'">The data is stale</div>
          
    <div *ngSwitchCase="'error'">There is an error</div>
        
  </div>
</ng-container>

Template utilities

lib-data-loader

This abstraction simplifies the process of loading an observable in template. Access it by importing NgxDataLoaderModule

Then add the component inside your template and pass an observable via dataSource input.

<ngx-data-loader [dataSource]="data$">
  <ng-template #content let-data>
    <span class="data">{{ data }}</span>
  </ng-template>
  <ng-template #loading>
    <span class="loading">Loading...</span>
  </ng-template>
  <ng-template #error let-error>
    <span class="error">{{ error }}</span>
  </ng-template>
</ngx-data-loader>

Use a template with the selector #content to define the elements to render when data is loaded, you can access the results of the observable by using a template let-X variable, X can be anything as the data is provided through $implicit context

Use the #loading and #error templates to display loading screen and error information respectively. Error data can be accessed via template context as well.

Gotcha: the loading template is only shown during the initial loading of the observable, if only the [dataSource] observable is provided.