Skip to content

Commit 69794b4

Browse files
authored
Merge pull request #2379 from the-guild-org/powerkiki2
New `onlyComplete()` helper to filter only complete results
2 parents f36206a + 7e4a609 commit 69794b4

File tree

7 files changed

+132
-8
lines changed

7 files changed

+132
-8
lines changed

.changeset/olive-dogs-watch.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'apollo-angular': minor
3+
---
4+
5+
New `onlyComplete()` helper to filter only complete results
6+
7+
If you use this, you should probably combine it with [`notifyOnNetworkStatusChange`](https://www.apollographql.com/docs/react/data/queries#queryhookoptions-interface-notifyonnetworkstatuschange).
8+
This tells `@apollo/client` to not emit the first `partial` result, so
9+
`apollo-angular` does not need to filter it out. The overall behavior is
10+
identical, but it saves some CPU cycles.
11+
12+
So something like this:
13+
14+
```ts
15+
apollo
16+
.watchQuery({
17+
query: myQuery,
18+
notifyOnNetworkStatusChange: false, // Adding this will save CPU cycles
19+
})
20+
.valueChanges
21+
.pipe(onlyComplete())
22+
.subscribe(result => {
23+
// Do something with complete result
24+
});
25+
```

packages/apollo-angular/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export { Subscription } from './subscription';
88
export { APOLLO_OPTIONS, APOLLO_NAMED_OPTIONS, APOLLO_FLAGS } from './tokens';
99
export type { Flags, NamedOptions, ResultOf, VariablesOf } from './types';
1010
export { gql } from './gql';
11+
export { onlyComplete } from './only-complete';
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { filter, type OperatorFunction } from 'rxjs';
2+
import type { ObservableQuery } from '@apollo/client/core';
3+
4+
/**
5+
* Filter emitted results to only receive results that are complete (`result.dataState === 'complete'`).
6+
*
7+
* This is a small wrapper around rxjs `filter()` for convenience only.
8+
*
9+
* If you use this, you should probably combine it with [`notifyOnNetworkStatusChange`](https://www.apollographql.com/docs/react/data/queries#queryhookoptions-interface-notifyonnetworkstatuschange).
10+
* This tells `@apollo/client` to not emit the first `partial` result, so `apollo-angular` does
11+
* not need to filter it out. The overall behavior is identical, but it saves some CPU cycles.
12+
*
13+
* So something like this:
14+
*
15+
* ```ts
16+
* apollo
17+
* .watchQuery({
18+
* query: myQuery,
19+
* notifyOnNetworkStatusChange: false, // Adding this will save CPU cycles
20+
* })
21+
* .valueChanges
22+
* .pipe(onlyComplete())
23+
* .subscribe(result => {
24+
* // Do something with complete result
25+
* });
26+
* ```
27+
*/
28+
export function onlyComplete<TData>(): OperatorFunction<
29+
ObservableQuery.Result<TData>,
30+
ObservableQuery.Result<TData, 'complete'>
31+
> {
32+
return filter(
33+
(result): result is ObservableQuery.Result<TData, 'complete'> =>
34+
result.dataState === 'complete',
35+
);
36+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { onlyComplete } from 'apollo-angular';
2+
import { Subject } from 'rxjs';
3+
import { describe, expect, test } from 'vitest';
4+
import { NetworkStatus, ObservableQuery } from '@apollo/client/core';
5+
6+
interface Result {
7+
user: {
8+
name: string;
9+
};
10+
}
11+
12+
describe('onlyComplete', () => {
13+
let theUser: Result['user'] | null = null;
14+
let count = 0;
15+
16+
test('should receive only complete results', () =>
17+
new Promise<void>(done => {
18+
const b = new Subject<ObservableQuery.Result<Result>>();
19+
b.pipe(onlyComplete()).subscribe({
20+
next: result => {
21+
count++;
22+
theUser = result.data.user;
23+
},
24+
complete: () => {
25+
expect(count).toBe(1);
26+
expect(theUser).toEqual({ name: 'foo' });
27+
done();
28+
},
29+
});
30+
31+
b.next({
32+
dataState: 'partial',
33+
data: {},
34+
loading: true,
35+
partial: true,
36+
networkStatus: NetworkStatus.loading,
37+
} satisfies ObservableQuery.Result<Result, 'partial'>);
38+
39+
b.next({
40+
dataState: 'complete',
41+
data: { user: { name: 'foo' } },
42+
loading: false,
43+
partial: false,
44+
networkStatus: NetworkStatus.ready,
45+
} satisfies ObservableQuery.Result<Result, 'complete'>);
46+
47+
b.next({
48+
dataState: 'partial',
49+
data: {},
50+
loading: true,
51+
partial: true,
52+
networkStatus: NetworkStatus.loading,
53+
} satisfies ObservableQuery.Result<Result, 'partial'>);
54+
55+
b.complete();
56+
}));
57+
});

packages/demo/src/app/app.component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { Apollo, gql, onlyComplete } from 'apollo-angular';
2+
import { Subject } from 'rxjs';
13
import { Component } from '@angular/core';
24
import { RouterLink, RouterOutlet } from '@angular/router';
5+
import type { ObservableQuery } from '@apollo/client/core';
36

47
@Component({
58
selector: 'app-root',

packages/demo/src/app/pages/movie/movie-page.component.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Apollo, gql } from 'apollo-angular';
1+
import { Apollo, gql, onlyComplete } from 'apollo-angular';
22
import { Observable } from 'rxjs';
3-
import { filter, map } from 'rxjs/operators';
3+
import { map } from 'rxjs/operators';
44
import { AsyncPipe } from '@angular/common';
55
import { Component, inject, OnInit } from '@angular/core';
66
import { ActivatedRoute, RouterLink } from '@angular/router';
@@ -69,10 +69,11 @@ export class MoviePageComponent implements OnInit {
6969
variables: {
7070
id: this.route.snapshot.paramMap.get('id')!,
7171
},
72+
notifyOnNetworkStatusChange: false,
7273
})
7374
.valueChanges.pipe(
74-
map(result => (result.dataState === 'complete' ? result.data.film : null)),
75-
filter(Boolean),
75+
onlyComplete(),
76+
map(result => result.data.film),
7677
);
7778
}
7879
}

packages/demo/src/app/pages/movies/movies-page.component.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Apollo, gql } from 'apollo-angular';
1+
import { Apollo, gql, onlyComplete } from 'apollo-angular';
22
import { Observable } from 'rxjs';
3-
import { filter, map } from 'rxjs/operators';
3+
import { map } from 'rxjs/operators';
44
import { AsyncPipe } from '@angular/common';
55
import { Component, inject, OnInit } from '@angular/core';
66
import { RouterLink } from '@angular/router';
@@ -56,10 +56,11 @@ export class MoviesPageComponent implements OnInit {
5656
}
5757
}
5858
`,
59+
notifyOnNetworkStatusChange: false,
5960
})
6061
.valueChanges.pipe(
61-
map(result => (result.dataState == 'complete' ? result.data.allFilms.films : null)),
62-
filter(Boolean),
62+
onlyComplete(),
63+
map(result => result.data.allFilms.films),
6364
);
6465
}
6566
}

0 commit comments

Comments
 (0)