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
25 changes: 25 additions & 0 deletions .changeset/olive-dogs-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'apollo-angular': minor
---

New `onlyComplete()` helper to filter only complete results

If you use this, you should probably combine it with [`notifyOnNetworkStatusChange`](https://www.apollographql.com/docs/react/data/queries#queryhookoptions-interface-notifyonnetworkstatuschange).
This tells `@apollo/client` to not emit the first `partial` result, so
`apollo-angular` does not need to filter it out. The overall behavior is
identical, but it saves some CPU cycles.

So something like this:

```ts
apollo
.watchQuery({
query: myQuery,
notifyOnNetworkStatusChange: false, // Adding this will save CPU cycles
})
.valueChanges
.pipe(onlyComplete())
.subscribe(result => {
// Do something with complete result
});
```
1 change: 1 addition & 0 deletions packages/apollo-angular/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export { Subscription } from './subscription';
export { APOLLO_OPTIONS, APOLLO_NAMED_OPTIONS, APOLLO_FLAGS } from './tokens';
export type { Flags, NamedOptions, ResultOf, VariablesOf } from './types';
export { gql } from './gql';
export { onlyComplete } from './only-complete';
36 changes: 36 additions & 0 deletions packages/apollo-angular/src/only-complete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { filter, type OperatorFunction } from 'rxjs';
import type { ObservableQuery } from '@apollo/client/core';

/**
* Filter emitted results to only receive results that are complete (`result.dataState === 'complete'`).
*
* This is a small wrapper around rxjs `filter()` for convenience only.
*
* If you use this, you should probably combine it with [`notifyOnNetworkStatusChange`](https://www.apollographql.com/docs/react/data/queries#queryhookoptions-interface-notifyonnetworkstatuschange).
* This tells `@apollo/client` to not emit the first `partial` result, so `apollo-angular` does
* not need to filter it out. The overall behavior is identical, but it saves some CPU cycles.
*
* So something like this:
*
* ```ts
* apollo
* .watchQuery({
* query: myQuery,
* notifyOnNetworkStatusChange: false, // Adding this will save CPU cycles
* })
* .valueChanges
* .pipe(onlyComplete())
* .subscribe(result => {
* // Do something with complete result
* });
* ```
*/
export function onlyComplete<TData>(): OperatorFunction<
ObservableQuery.Result<TData>,
ObservableQuery.Result<TData, 'complete'>
> {
return filter(
(result): result is ObservableQuery.Result<TData, 'complete'> =>
result.dataState === 'complete',
);
}
57 changes: 57 additions & 0 deletions packages/apollo-angular/testing/tests/only-complete.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { onlyComplete } from 'apollo-angular';
import { Subject } from 'rxjs';
import { describe, expect, test } from 'vitest';
import { NetworkStatus, ObservableQuery } from '@apollo/client/core';

interface Result {
user: {
name: string;
};
}

describe('onlyComplete', () => {
let theUser: Result['user'] | null = null;
let count = 0;

test('should receive only complete results', () =>
new Promise<void>(done => {
const b = new Subject<ObservableQuery.Result<Result>>();
b.pipe(onlyComplete()).subscribe({
next: result => {
count++;
theUser = result.data.user;
},
complete: () => {
expect(count).toBe(1);
expect(theUser).toEqual({ name: 'foo' });
done();
},
});

b.next({
dataState: 'partial',
data: {},
loading: true,
partial: true,
networkStatus: NetworkStatus.loading,
} satisfies ObservableQuery.Result<Result, 'partial'>);

b.next({
dataState: 'complete',
data: { user: { name: 'foo' } },
loading: false,
partial: false,
networkStatus: NetworkStatus.ready,
} satisfies ObservableQuery.Result<Result, 'complete'>);

b.next({
dataState: 'partial',
data: {},
loading: true,
partial: true,
networkStatus: NetworkStatus.loading,
} satisfies ObservableQuery.Result<Result, 'partial'>);

b.complete();
}));
});
3 changes: 3 additions & 0 deletions packages/demo/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Apollo, gql, onlyComplete } from 'apollo-angular';
import { Subject } from 'rxjs';
import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
import type { ObservableQuery } from '@apollo/client/core';

@Component({
selector: 'app-root',
Expand Down
9 changes: 5 additions & 4 deletions packages/demo/src/app/pages/movie/movie-page.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Apollo, gql } from 'apollo-angular';
import { Apollo, gql, onlyComplete } from 'apollo-angular';
import { Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { map } from 'rxjs/operators';
import { AsyncPipe } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
import { ActivatedRoute, RouterLink } from '@angular/router';
Expand Down Expand Up @@ -69,10 +69,11 @@ export class MoviePageComponent implements OnInit {
variables: {
id: this.route.snapshot.paramMap.get('id')!,
},
notifyOnNetworkStatusChange: false,
})
.valueChanges.pipe(
map(result => (result.dataState === 'complete' ? result.data.film : null)),
filter(Boolean),
onlyComplete(),
map(result => result.data.film),
);
}
}
9 changes: 5 additions & 4 deletions packages/demo/src/app/pages/movies/movies-page.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Apollo, gql } from 'apollo-angular';
import { Apollo, gql, onlyComplete } from 'apollo-angular';
import { Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { map } from 'rxjs/operators';
import { AsyncPipe } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
import { RouterLink } from '@angular/router';
Expand Down Expand Up @@ -56,10 +56,11 @@ export class MoviesPageComponent implements OnInit {
}
}
`,
notifyOnNetworkStatusChange: false,
})
.valueChanges.pipe(
map(result => (result.dataState == 'complete' ? result.data.allFilms.films : null)),
filter(Boolean),
onlyComplete(),
map(result => result.data.allFilms.films),
);
}
}
Loading