Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export class TodosService {
For methods that require a `queryFn` parameter like
`ensureQueryData`, `fetchQuery`, `prefetchQuery`, `fetchInfiniteQuery` and `prefetchInfiniteQuery` it's possible to use both Promises and Observables. See an example [here](https://github.com/ngneat/query/blob/main/src/app/prefetch-page/resolve.ts#L9).


#### Component Usage - Observable

To get an observable use the `result$` property:
Expand Down Expand Up @@ -626,6 +627,50 @@ class TodoComponent {
}
```

## Custom Queries objects

All injected queries objects got from the following inject functions could be overwritten by using `provideQueryConfig` function:
- `injectQuery()`
- `injectMutation()`
- `injectIsMutating()`
- `injectIsFetching()`
- `injectInfiniteQuery()`

All `provideQueryConfig` parameter's properties are optional, and you can use raw object or object factory.

```ts
export function provideQueryConfig(
config: {
query?: QueryObject | (() => QueryObject);
mutation?: MutationObject | (() => MutationObject);
isMutating?: IsMutatingObject | (() => IsMutatingObject);
isFetching?: IsFetchingObject | (() => IsFetchingObject);
infiniteQuery?: InfiniteQueryObject | (() => InfiniteQueryObject);
},
): Provider
```

### Mock Example
```ts
import { provideQueryConfig } from '@ngneat/query';

const queryMock = {
use: () => ({
result$: of({ ... }),
result: computed(() => ({ ... })),
})
};

provideQueryConfig({
query: queryMock, // or query: () => queryMock if you need a factory
});

const query = injectQuery();
query({ ... }).result() // you can call query from your custom query mock
```



## Devtools

Install the `@ngneat/query-devtools` package. Lazy load and use it only in `development` environment:
Expand Down
1 change: 1 addition & 0 deletions query/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export {
createSuccessObserverResult,
toPromise,
} from './lib/utils';
export { provideQueryConfig } from './lib/provide-query-config';
69 changes: 57 additions & 12 deletions query/src/lib/infinite-query.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import {
assertInInjectionContext,
inject,
Injectable,
InjectionToken,
Injector,
runInInjectionContext,
} from '@angular/core';
import { injectQueryClient } from './query-client';

import {
DefaultError,
InfiniteData,
Expand All @@ -24,6 +23,17 @@ import {
} from './base-query';
import { Result } from './types';

/** @internal */
export const InfiniteQueryToken = new InjectionToken<InfiniteQuery>(
'InfiniteQuery',
{
providedIn: 'root',
factory() {
return new InfiniteQuery();
},
},
);

interface _CreateInfiniteQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
Expand Down Expand Up @@ -61,8 +71,28 @@ export type CreateInfiniteQueryOptions<
queryFn: QueryFunctionWithObservable<TQueryFnData, TQueryKey, TPageParam>;
};

@Injectable({ providedIn: 'root' })
class InfiniteQuery {
export interface InfiniteQueryObject {
use: <
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
options: CreateInfiniteQueryOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
) => Result<InfiniteQueryObserverResult<TData, TError>>;
}

/** @internal
* only exported for @test
*/
class InfiniteQuery implements InfiniteQueryObject {
#instance = injectQueryClient();
#injector = inject(Injector);

Expand Down Expand Up @@ -90,18 +120,33 @@ class InfiniteQuery {
}
}

function infiniteQueryUseFnFromToken() {
const infiniteQuery = inject(InfiniteQueryToken);
return infiniteQuery.use.bind(infiniteQuery);
}

/**
*
* Optionally pass an injector that will be used than the current one.
* Can be useful if you want to use it in ngOnInit hook for example.
*
* @example
*
* injector = inject(Injector);
*
* ngOnInit() {
* const infiniteQuery = injectInfiniteQuery({ injector: this.injector });
* }
*
*/
export function injectInfiniteQuery(options?: { injector?: Injector }) {
if (options?.injector) {
return runInInjectionContext(options.injector, () => {
const query = inject(InfiniteQuery);

return query.use.bind(query);
});
return runInInjectionContext(options.injector, () =>
infiniteQueryUseFnFromToken(),
);
}

assertInInjectionContext(injectInfiniteQuery);

const query = inject(InfiniteQuery);

return query.use.bind(query);
return infiniteQueryUseFnFromToken();
}
61 changes: 49 additions & 12 deletions query/src/lib/is-fetching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,33 @@ import { injectQueryClient } from './query-client';
import {
assertInInjectionContext,
inject,
Injectable,
InjectionToken,
Injector,
runInInjectionContext,
Signal,
} from '@angular/core';
import { distinctUntilChanged, Observable } from 'rxjs';
import { toSignal } from '@angular/core/rxjs-interop';

@Injectable({ providedIn: 'root' })
export class IsFetching {
/** @internal */
export const IsFetchingToken = new InjectionToken<IsFetching>('IsFetching', {
providedIn: 'root',
factory() {
return new IsFetching();
},
});

export interface IsFetchingObject {
use: (filters?: QueryFilters) => {
result$: Observable<number>;
toSignal: () => Signal<number | undefined>;
};
}

/** @internal
* only exported for @test
*/
export class IsFetching implements IsFetchingObject {
#queryClient = injectQueryClient();

use(filters?: QueryFilters) {
Expand All @@ -32,15 +51,33 @@ export class IsFetching {
}
}

const UseIsFetching = new InjectionToken<IsFetching['use']>('UseIsFetching', {
providedIn: 'root',
factory() {
const isFetching = new IsFetching();
return isFetching.use.bind(isFetching);
},
});
function isFetchingUseFnFromToken() {
const isFetching = inject(IsFetchingToken);
return isFetching.use.bind(isFetching);
}

/**
*
* Optionally pass an injector that will be used than the current one.
* Can be useful if you want to use it in ngOnInit hook for example.
*
* @example
*
* injector = inject(Injector);
*
* ngOnInit() {
* const isFetching = injectIsFetching({ injector: this.injector });
* }
*
*/
export function injectIsFetching(options?: { injector?: Injector }) {
if (options?.injector) {
return runInInjectionContext(options.injector, () =>
isFetchingUseFnFromToken(),
);
}

export function injectIsFetching() {
assertInInjectionContext(injectIsFetching);
return inject(UseIsFetching);

return isFetchingUseFnFromToken();
}
60 changes: 48 additions & 12 deletions query/src/lib/is-mutating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,33 @@ import { injectQueryClient } from './query-client';
import {
assertInInjectionContext,
inject,
Injectable,
InjectionToken,
Injector,
runInInjectionContext,
Signal,
} from '@angular/core';
import { distinctUntilChanged, Observable } from 'rxjs';
import { toSignal } from '@angular/core/rxjs-interop';

@Injectable({ providedIn: 'root' })
export class IsMutating {
/** @internal */
export const IsMutatingToken = new InjectionToken<IsMutating>('IsMutating', {
providedIn: 'root',
factory() {
return new IsMutating();
},
});

export interface IsMutatingObject {
use: (filters?: MutationFilters) => {
result$: Observable<number>;
toSignal: () => Signal<number | undefined>;
};
}

/** @internal
* only exported for @test
*/
export class IsMutating implements IsMutatingObject {
#queryClient = injectQueryClient();

use(filters?: MutationFilters) {
Expand All @@ -34,16 +53,33 @@ export class IsMutating {
}
}

const UseIsMutating = new InjectionToken<IsMutating['use']>('UseIsFetching', {
providedIn: 'root',
factory() {
const isMutating = new IsMutating();
return isMutating.use.bind(isMutating);
},
});
function isMutatingUseFnFromToken() {
const isMutating = inject(IsMutatingToken);
return isMutating.use.bind(isMutating);
}

/**
*
* Optionally pass an injector that will be used than the current one.
* Can be useful if you want to use it in ngOnInit hook for example.
*
* @example
*
* injector = inject(Injector);
*
* ngOnInit() {
* const isMutating = injectIsMutating({ injector: this.injector });
* }
*
*/
export function injectIsMutating(options?: { injector?: Injector }) {
if (options?.injector) {
return runInInjectionContext(options.injector, () =>
isMutatingUseFnFromToken(),
);
}

export function injectIsMutating() {
assertInInjectionContext(injectIsMutating);

return inject(UseIsMutating);
return isMutatingUseFnFromToken();
}
Loading