(`https://jsonplaceholder.typicode.com/todos`, {
241 | title,
242 | }),
243 | });
244 | }
245 | }
246 | ```
247 |
248 | The `variables` in the `mutationFn` callback are the variables that will be passed to the `mutate` function later.
249 |
250 | Now create your component in which you want to use your newly created service:
251 |
252 | ```ts
253 | @Component({
254 | template: `
255 |
256 |
257 |
258 | @if (addTodo.result$ | async; as result) {
259 | @if (result.isLoading) {
260 | Mutation is loading
261 | }
262 | @if (result.isSuccess) {
263 | Mutation was successful
264 | }
265 | @if (result.isError) {
266 | Mutation encountered an Error
267 | }
268 | }
269 | `,
270 | })
271 | export class TodosComponent {
272 | addTodo = inject(TodosService).addTodo();
273 |
274 | onAddTodo({ title }) {
275 | this.addTodo.mutate({ title });
276 | // Or
277 | this.addTodo.mutateAsync({ title });
278 | }
279 | }
280 | ```
281 |
282 | If you prefer a signal based approach, then you can use the `result` getter function on `addTodo`.
283 |
284 | ```ts
285 | @Component({
286 | template: `
287 |
288 |
289 |
290 | @if (addTodo.result(); as result) {
291 | @if (result.isLoading) {
292 | Mutation is loading
293 | }
294 | @if (result.isSuccess) {
295 | Mutation was successful
296 | }
297 | @if (result.isError) {
298 | Mutation encountered an Error
299 | }
300 | }
301 | `,
302 | })
303 | export class TodosComponent {
304 | addTodo = inject(TodosService).addTodo();
305 |
306 | onAddTodo({ title }) {
307 | this.addTodo.mutate({ title });
308 | }
309 | }
310 | ```
311 |
312 | A more in depth [example](https://github.com/ngneat/query/blob/next/src/app/mutation-page/) can be found on our playground.
313 |
314 | ## Query Global Options
315 |
316 | You can inject a default config for the underlying `@tanstack/query` instance by using the `provideQueryClientOptions({})` function.
317 |
318 | ```ts
319 | import { provideQueryClientOptions } from '@ngneat/query';
320 |
321 | bootstrapApplication(AppComponent, {
322 | providers: [
323 | provideQueryClientOptions({
324 | defaultOptions: {
325 | queries: {
326 | staleTime: 3000,
327 | },
328 | },
329 | }),
330 | ],
331 | });
332 | ```
333 |
334 | It accept also a function factory if you need an injection context while creating the configuration.
335 |
336 | ```ts
337 | import { provideQueryClientOptions } from '@ngneat/query';
338 |
339 | const withFunctionalFactory: QueryClientConfigFn = () => {
340 | const notificationService = inject(NotificationService);
341 |
342 | return {
343 | queryCache: new QueryCache({
344 | onError: (error: Error) => notificationService.notifyError(error),
345 | }),
346 | defaultOptions: {
347 | queries: {
348 | staleTime: 3000,
349 | },
350 | },
351 | };
352 | };
353 |
354 | bootstrapApplication(AppComponent, {
355 | providers: [provideQueryClientOptions(withFunctionalFactory)],
356 | });
357 | ```
358 |
359 | ## Signal Utils
360 |
361 | ### intersectResults
362 |
363 | The `intersectResults` function is used to merge multiple **_signal_** queries into one.
364 | It will return a new base query result that will merge the results of all the queries.
365 |
366 | > **Note:** The data will only be mapped if the result is **successful** and otherwise just returned as is on **any other** state.
367 |
368 | ```ts
369 | import { intersectResults } from '@ngneat/query';
370 |
371 | @Component({
372 | standalone: true,
373 | template: `
374 | Signals Intersection
375 | @if (intersection(); as intersectionResult) {
376 | @if (intersectionResult.isLoading) {
377 | Loading
378 | }
379 | @if (intersectionResult.isSuccess) {
380 | {{ intersectionResult.data }}
381 | }
382 | @if (intersectionResult.isError) {
383 | Error
384 | }
385 | }
386 | `,
387 | changeDetection: ChangeDetectionStrategy.OnPush,
388 | })
389 | export class TodosPageComponent {
390 | #todosService = inject(TodosService);
391 |
392 | intersection = intersectResults(
393 | [
394 | this.#todosService.getTodo('1').result,
395 | this.#todosService.getTodo('2').result,
396 | ],
397 | ([todoOne, todoTwo]) => todoOne.title + todoTwo.title,
398 | );
399 |
400 | intersectionAsObject = intersectResults(
401 | {
402 | todoOne: this.#todosService.getTodo('1').result,
403 | todoTwo: this.#todosService.getTodo('2').result,
404 | },
405 | ({ todoOne, todoTwo }) => todoOne.title + todoTwo.title,
406 | );
407 | }
408 | ```
409 |
410 | ## RxJS Operators
411 |
412 | ### filterSuccessResult
413 |
414 | The `filterSuccessResult` operator is useful when you want to filter only successful results:
415 |
416 | `todosService.getTodos().result$.pipe(filterSuccessResult())`
417 |
418 | ### filterErrorResult
419 |
420 | The `filterErrorResult` operator is useful when you want to filter only error results:
421 |
422 | `todosService.getTodos().result$.pipe(filterErrorResult())`
423 |
424 | ### tapSuccessResult
425 |
426 | The `tapSuccessResult` operator is useful when you want to run a side effect only when the result is successful:
427 |
428 | `todosService.getTodos().result$.pipe(tapSuccessResult(console.log))`
429 |
430 | ### tapErrorResult
431 |
432 | The `tapErrorResult` operator is useful when you want to run a side effect only when the result is erroring:
433 |
434 | `todosService.getTodos().result$.pipe(tapErrorResult(console.log))`
435 |
436 | ### mapResultData
437 |
438 | The `mapResultData` operator maps the `data` property of the `result` object in case of a successful result.
439 |
440 | ```ts
441 | this.todosService.getTodos().result$.pipe(
442 | mapResultData((data) => {
443 | return {
444 | todos: data.todos.filter(predicate),
445 | };
446 | }),
447 | );
448 | ```
449 |
450 | ### takeUntilResultFinalize
451 |
452 | An operator that takes values emitted by the source observable until the `isFetching` property on the result is false.
453 | It is intended to be used in scenarios where an observable stream should be listened to until the result has finished fetching (e.g success or error).
454 |
455 | `todosService.getTodos().result$.pipe(takeUntilResultFinalize())`
456 |
457 | ### takeUntilResultSuccess
458 |
459 | An operator that takes values emitted by the source observable until the `isSuccess` property on the result is true.
460 | It is intended to be used in scenarios where an observable stream should be listened to until a successful result is emitted.
461 |
462 | `todosService.getTodos().result$.pipe(takeUntilResultSuccess())`
463 |
464 | ### takeUntilResultError()
465 |
466 | An operator that takes values emitted by the source observable until the `isError` property on the result is true.
467 | It is intended to be used in scenarios where an observable stream should be listened to until an error result is emitted.
468 |
469 | `todosService.getTodos().result$.pipe(takeUntilResultError())`
470 |
471 | ### startWithPendingQueryResult
472 |
473 | Starts the observable stream with a pending query result that would also be returned upon creating a normal query:
474 |
475 | ```ts
476 | this.todosService.getTodos().result$.pipe(
477 | filterSuccess(),
478 | switchMap(() => someSource),
479 | startWithPendingQueryResult(),
480 | );
481 | ```
482 |
483 | ### intersectResults$
484 |
485 | The `intersectResults$` operator is used to merge multiple **_observable_** queries into one, this is usually done with a `combineLatest`.
486 | It will return a new base query result that will merge the results of all the queries.
487 |
488 | > **Note:** The data will only be mapped if the result is **successful** and otherwise just returned as is on **any other** state.
489 |
490 | ```ts
491 | const query = combineLatest({
492 | todos: todos.result$,
493 | posts: posts.result$,
494 | }).pipe(
495 | intersectResults$(({ todos, posts }) => { ... })
496 | )
497 |
498 | const query = combineLatest([todos.result$, posts.result$]).pipe(
499 | intersectResults$(([todos, posts]) => { ... })
500 | )
501 | ```
502 |
503 | ## Utils
504 |
505 | - `createSuccessObserverResult` - Create success observer result:
506 |
507 | ```
508 | import { createSyncObserverResult } from '@ngneat/query';
509 |
510 | result = of(createSuccessObserverResult(data))
511 | ```
512 |
513 | - `createPendingObserverResult` - Create pending observer result
514 |
515 | - `updateOptions` - In cases that you want to use the same observer result and update the options you can use the `updateOptions` method:
516 |
517 | ```ts
518 | @Component({
519 | standalone: true,
520 | imports: [RouterModule],
521 | template: `
522 | User 2
523 |
524 | @if (user().isLoading) {
525 |
526 | }
527 |
528 | @if (user().data; as user) {
529 | {{ user.email }}
530 | }
531 | `,
532 | })
533 | export class UsersPageComponent {
534 | usersService = inject(UsersService);
535 | userResultDef = this.usersService.getUser(
536 | +inject(ActivatedRoute).snapshot.queryParams['id'],
537 | );
538 |
539 | user = this.userResultDef.result;
540 |
541 | @Input()
542 | set id(userId: string) {
543 | this.userResultDef.updateOptions(this.usersService.getUserOptions(+userId));
544 | }
545 | }
546 | ```
547 |
548 | ## Type Utils
549 |
550 | - `ObservableQueryResult` - Alias for `Observable>`
551 | - `SignalQueryResult` - Alias for `Signal>`
552 |
553 | ## Is Fetching
554 |
555 | `injectIsFetching` is a function that returns the number of the queries that your application is loading or fetching in the background (useful for app-wide loading indicators).
556 |
557 | ### Observable Example
558 |
559 | ```ts
560 | import { injectIsFetching } from '@ngneat/query';
561 |
562 | class TodoComponent {
563 | #isFetching = injectIsFetching();
564 |
565 | // How many queries overall are currently fetching data?
566 | public isFetching$ = this.#isFetching().result$;
567 |
568 | // How many queries matching the todos prefix are currently fetching?
569 | public isFetchingTodos$ = this.#isFetching({ queryKey: ['todos'] }).result$;
570 | }
571 | ```
572 |
573 | ### Signal Example
574 |
575 | ```ts
576 | import { injectIsFetching } from '@ngneat/query';
577 |
578 | class TodoComponent {
579 | #isFetching = injectIsFetching();
580 |
581 | // How many queries overall are currently fetching data?
582 | public isFetching = this.#isFetching().result;
583 |
584 | // How many queries matching the todos prefix are currently fetching?
585 | public isFetchingTodos = this.#isFetching({
586 | queryKey: ['todos'],
587 | }).result;
588 | }
589 | ```
590 |
591 | ## Is Mutating
592 |
593 | `injectIsMutating` is an optional hook that returns the number of mutations that your application is fetching (useful for app-wide loading indicators).
594 |
595 | ### Observable Example
596 |
597 | ```ts
598 | import { injectIsMutating } from '@ngneat/query';
599 |
600 | class TodoComponent {
601 | #isMutating = injectIsMutating();
602 |
603 | // How many queries overall are currently fetching data?
604 | public isFetching$ = this.#isMutating().result$;
605 |
606 | // How many queries matching the todos prefix are currently fetching?
607 | public isFetchingTodos$ = this.#isMutating({ queryKey: ['todos'] }).result$;
608 | }
609 | ```
610 |
611 | ### Signal Example
612 |
613 | ```ts
614 | import { injectIsMutating } from '@ngneat/query';
615 |
616 | class TodoComponent {
617 | #isMutating = injectIsMutating();
618 |
619 | // How many queries overall are currently fetching data?
620 | public isFetching = this.#isMutating().result;
621 |
622 | // How many queries matching the todos prefix are currently fetching?
623 | public isFetchingTodos = this.#isMutating({
624 | queryKey: ['todos'],
625 | }).result;
626 | }
627 | ```
628 |
629 | ## Devtools
630 |
631 | Install the `@ngneat/query-devtools` package. Lazy load and use it only in `development` environment:
632 |
633 | ```ts
634 | import { provideQueryDevTools } from '@ngneat/query-devtools';
635 | import { environment } from 'src/environments/environment';
636 |
637 | bootstrapApplication(AppComponent, {
638 | providers: [environment.production ? [] : provideQueryDevTools(options)],
639 | });
640 | ```
641 |
642 | See all the avilable options [here](https://tanstack.com/query/v5/docs/react/devtools#options).
643 |
644 | ## SSR
645 |
646 | On the Server:
647 |
648 | ```ts
649 | import { provideQueryClient } from '@ngneat/query';
650 | import { QueryClient, dehydrate } from '@tanstack/query-core';
651 | import { renderApplication } from '@angular/platform-server';
652 |
653 | async function handleRequest(req, res) {
654 | const queryClient = new QueryClient();
655 | let html = await renderApplication(AppComponent, {
656 | providers: [provideQueryClient(queryClient)],
657 | });
658 |
659 | const queryState = JSON.stringify(dehydrate(queryClient));
660 | html = html.replace(
661 | '',
662 | `