GithubHelp home page GithubHelp logo

joaopaulolousada / query Goto Github PK

View Code? Open in Web Editor NEW

This project forked from ngneat/query

0.0 0.0 0.0 1.24 MB

🚀 Powerful asynchronous state management, server-state utilities and data fetching for Angular Applications

Home Page: https://ngneat.github.io/query

JavaScript 1.60% TypeScript 97.91% HTML 0.47% SCSS 0.02%

query's Introduction

The TanStack Query (also known as react-query) adapter for Angular applications

Get rid of granular state management, manual refetching, and async spaghetti code. TanStack Query gives you declarative, always-up-to-date auto-managed queries and mutations that improve your developer experience.

Features

✅  Backend agnostic
✅  Dedicated Devtools
✅  Auto Caching
✅  Auto Refetching
✅  Window Focus Refetching
✅  Polling/Realtime Queries
✅  Parallel Queries
✅  Dependent Queries
✅  Mutations API
✅  Automatic Garbage Collection
✅  Paginated/Cursor Queries
✅  Load-More/Infinite Scroll Queries
✅  Request Cancellation
✅  Prefetching
✅  Offline Support
✅  Data Selectors


Build Status commitizen PRs coc-badge semantic-release styled with prettier spectator

Table of Contents

Installation

npm i @ngneat/query
yarn add @ngneat/query

Queries

Query Client

Inject the QueryClientService provider to get access to the query client instance:

import { QueryClientService } from '@ngneat/query';

@Injectable({ providedIn: 'root' })
export class TodosService {
  private queryClient = inject(QueryClientService);
}

Query

Inject the UseQuery in your service. Using the hook is similar to the official hook, except the query function should return an observable.

import { UseQuery } from '@ngneat/query';

@Injectable({ providedIn: 'root' })
export class TodosService {
  private http = inject(HttpClient);
  private useQuery = inject(UseQuery);

  getTodos() {
    return this.useQuery(['todos'], () => {
      return this.http.get<Todo[]>(
        'https://jsonplaceholder.typicode.com/todos'
      );
    });
  }

  getTodo(id: number) {
    return this.useQuery(['todo', id], () => {
      return this.http.get<Todo>(
        `https://jsonplaceholder.typicode.com/todos/${id}`
      );
    });
  }
}

Use it in your component:

import { SubscribeModule } from '@ngneat/subscribe';

@Component({
  standalone: true,
  imports: [NgIf, NgForOf, SpinnerComponent, SubscribeModule],
  template: `
    <ng-container *subscribe="todos$ as todos">
      <ng-query-spinner *ngIf="todos.isLoading"></ng-query-spinner>

      <p *ngIf="todos.isError">Error...</p>

      <ul *ngIf="todos.isSuccess">
        <li *ngFor="let todo of todos.data">
          {{ todo.title }}
        </li>
      </ul>
    </ng-container>
  `,
})
export class TodosPageComponent {
  todos$ = inject(TodosService).getTodos().result$;
}

Note that using the *subscribe directive is optional. Subscriptions can be made using any method you choose.

Infinite Query

Inject the UseInfiniteQuery provider in your service. Using the hook is similar to the official hook, except the query function should return an observable.

import { UseInfiniteQuery } from '@ngneat/query';

@Injectable({ providedIn: 'root' })
export class ProjectsService {
  private useInfiniteQuery = inject(UseInfiniteQuery);

  getProjects() {
    return this.useInfiniteQuery(
      ['projects'],
      ({ pageParam = 0 }) => {
        return getProjects(pageParam);
      },
      {
        getNextPageParam(projects) {
          return projects.nextId;
        },
        getPreviousPageParam(projects) {
          return projects.previousId;
        },
      }
    );
  }
}

Checkout the complete example in our playground.

Persisted Query

Use the UsePersistedQuery provider when you want to use the keepPreviousData feature. For example, to implement the pagination functionality:

import { inject, Injectable } from '@angular/core';
import {
  UsePersistedQuery,
  QueryClientService,
  queryOptions,
} from '@ngneat/query';
import { firstValueFrom } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class PaginationService {
  private queryClient = inject(QueryClientService);

  getProjects = inject(UsePersistedQuery)((queryKey: ['projects', number]) => {
    return queryOptions({
      queryKey,
      queryFn: ({ queryKey }) => {
        return fetchProjects(queryKey[1]);
      },
    });
  });

  prefetch(page: number) {
    return this.queryClient.prefetchQuery(['projects', page], () =>
      firstValueFrom(fetchProjects(page))
    );
  }
}

Checkout the complete example in our playground.

Mutations

Mutation Result

The official mutation function can be a little verbose. Generally, you can use the following in-house simplified implementation.

import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { QueryClientService } from '@ngneat/query';

@Injectable({ providedIn: 'root' })
export class TodosService {
  private http = inject(HttpClient);
  private queryClient = inject(QueryClientService);

  addTodo({ title }: { title: string }) {
    return this.http.post<{ success: boolean }>(`todos`, { title }).pipe(
      tap((newTodo) => {
        // Invalidate to refetch
        this.queryClient.invalidateQueries(['todos']);
        // Or update manually
        this.queryClient.setQueryData<TodosResponse>(
          ['todos'],
          addEntity('todos', newTodo)
        );
      })
    );
  }
}

And in the component:

import { QueryClientService, useMutationResult } from '@ngneat/query';

@Component({
  template: `
    <input #ref />

    <button
      (click)="addTodo({ title: ref.value })"
      *subscribe="addTodoMutation.result$ as addTodoMutation"
    >
      Add todo {{ addTodoMutation.isLoading ? 'Loading' : '' }}
    </button>
  `,
})
export class TodosPageComponent {
  private todosService = inject(TodosService);
  addTodoMutation = useMutationResult();

  addTodo({ title }) {
    this.todosService
      .addTodo({ title })
      .pipe(this.addTodoMutation.track())
      .subscribe();
  }
}

Mutation

You can use the original mutation functionality if you prefer.

import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { QueryClientService, UseMutation } from '@ngneat/query';

@Injectable({ providedIn: 'root' })
export class TodosService {
  private http = inject(HttpClient);
  private queryClient = inject(QueryClientService);
  private useMutation = inject(UseMutation);

  addTodo() {
    return this.useMutation(({ title }: { title: string }) => {
      return this.http.post<{ success: boolean }>(`todos`, { title }).pipe(
        tap((newTodo) => {
          // Invalidate to refetch
          this.queryClient.invalidateQueries(['todos']);
          // Or update manually
          this.queryClient.setQueryData<TodosResponse>(
            ['todos'],
            addEntity('todos', newTodo)
          );
        })
      );
    });
  }
}

And in the component:

@Component({
  template: `
    <input #ref />

    <button
      (click)="addTodo({ title: ref.value })"
      *subscribe="addTodoMutation.result$ as addTodoMutation"
    >
      Add todo {{ addTodoMutation.isLoading ? 'Loading' : '' }}
    </button>
  `,
})
export class TodosPageComponent {
  private todosService = inject(TodosService);
  addTodoMutation = this.todosService.addTodo();

  addTodo({ title }) {
    this.addTodoMutation$.mutate({ title }).then((res) => {
      console.log(res.success);
    });
  }
}

Query Global Options

You can provide the QUERY_CLIENT_OPTIONS provider to set the global options of the query client instance:

import { provideQueryClientOptions } from '@ngneat/query';

{
  providers: [
    provideQueryClientOptions({
      defaultOptions: {
        queries: {
          staleTime: 3000,
        },
      },
    }),
  ];
}

Operators

import {
  filterError,
  filterSuccess,
  selectResult,
  mapResultData,
} from '@ngneat/query';

export class TodosPageComponent {
  todosService = inject(TodosService);

  ngOnInit() {
    this.todosService.getTodos().result$.pipe(filterError());
    this.todosService.getTodos().result$.pipe(filterSuccess());
    this.todosService
      .getTodos()
      .result$.pipe(selectResult((result) => result.data.foo));

    // Map the result `data`
    this.todosService.getTodos().pipe(
      mapResultData((data) => {
        return {
          todos: data.todos.filter(predicate),
        };
      })
    );
  }
}

Entity Utils

The library exposes the addEntity, and removeEntity helpers:

import {
  addEntity,
  QueryClientService,
  UseQuery,
  removeEntity,
} from '@ngneat/query';
import { tap } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class TodosService {
  private useQuery = inject(UseQuery);
  private queryClient = inject(QueryClientService);
  private http = inject(HttpClient);

  createTodo(body) {
    return this.http.post('todos', body).pipe(
      tap((newTodo) => {
        this.queryClient.setQueryData<TodosResponse>(
          ['todos'],
          addEntity('todos', newTodo)
        );
      })
    );
  }
}

Utils

Implementation of isFetching and isMutating.

import {
  UseIsFetching,
  UseIsMutating,
  createSyncObserverResult
} from '@ngneat/query';

// How many queries are fetching?
const isFetching$ = inject(UseIsFetching)();
// How many queries matching the posts prefix are fetching?
const isFetchingPosts$ = inject(UseIsFetching)(['posts']);

// How many mutations are fetching?
const isMutating$ = inject(UseIsMutating)();
// How many mutations matching the posts prefix are fetching?
const isMutatingPosts$ = inject(UseIsMutating)(['posts']);

// Create sync successful observer in case we want to work with one interface
of(createSyncObserverResult(data, options?))

Use Constructor DI

You can use the constructor version instead of inject:

QueryService.use(...)
PersistedQueryService.use(...)
InfiniteQueryService.use(...)
MutationService.use(...)

Devtools

Install the @ngneat/query-devtools package. Lazy load and use it only in development environment:

import { ENVIRONMENT_INITIALIZER } from '@angular/core';
import { environment } from './environments/environment';

import { QueryClientService } from '@ngneat/query';

bootstrapApplication(AppComponent, {
  providers: [
    environment.production
      ? []
      : {
          provide: ENVIRONMENT_INITIALIZER,
          multi: true,
          useValue() {
            const queryClient = inject(QueryClientService);
            import('@ngneat/query-devtools').then((m) => {
              m.ngQueryDevtools({ queryClient });
            });
          },
        },
  ],
});

SSR

On the Server:

import { provideQueryClient } from '@ngneat/query';
import { QueryClient, dehydrate } from '@tanstack/query-core';
import { renderApplication } from '@angular/platform-server';

async function handleRequest(req, res) {
  const queryClient = new QueryClient();
  let html = await renderApplication(AppComponent, {
    providers: [provideQueryClient(queryClient)],
  });
  const queryState = JSON.stringify(dehydrate(queryClient));
  html = html.replace(
    '</body>',
    `<script>window.__QUERY_STATE__ = ${queryState}</script></body>`
  );

  res.send(html);
  queryClient.clear();
}

Client:

import { importProvidersFrom } from '@angular/core';
import { bootstrapApplication, BrowserModule } from '@angular/platform-browser';
import { provideQueryClient } from '@ngneat/query';
import { QueryClient, hydrate } from '@tanstack/query-core';

const queryClient = new QueryClient();
const dehydratedState = JSON.parse(window.__QUERY_STATE__);
hydrate(queryClient, dehydratedState);

bootstrapApplication(AppComponent, {
  providers: [
    importProvidersFrom(
      BrowserModule.withServerTransition({ appId: 'server-app' })
    ),
    provideQueryClient(queryClient),
  ],
});

Created By

Netanel Basal
Netanel Basal

Contributors ✨

Thank goes to all these wonderful people who contributed ❤️

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.