Skip to content

Commit e490cd7

Browse files
committed
fix(resources): angular firestore collection() returns cached values
this is a warkaround for angular/angularfire#2012
1 parent 088b863 commit e490cd7

File tree

7 files changed

+89
-60
lines changed

7 files changed

+89
-60
lines changed
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import { createAction, union } from '@ngrx/store';
1+
import { createAction, union, props } from '@ngrx/store';
22

3-
export const loadResources = createAction('[Resources] Load Resources');
3+
export const loadResources = createAction(
4+
'[Resources] Load Resources',
5+
props<{ pageSize: number; orderByDirection: firebase.firestore.OrderByDirection }>()
6+
);
7+
export const loadNextResources = createAction('[Resources] Load Next Resources');
8+
export const clearResources = createAction('[Resources] Clear Resources');
49

510
/**
611
* Export a type alias of all actions in this action group
712
* so that reducers can easily compose action types
813
*/
9-
const all = union({ loadResources });
14+
const all = union({ loadResources, loadNextResources, clearResources });
1015

1116
export type ResourcesActionsUnion = typeof all;

src/app/web-portal/resources/components/resources-masonry/resources-masonry.component.html

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@
33
<div class="masonry-column-width" #masonryItemSizer></div>
44
</div>
55
<div>
6-
<ngx-masonry
7-
[options]="masonryOptions"
8-
[updateLayout]="updateMasonryLayout"
9-
[useImagesLoaded]="false"
10-
*ngIf="viewInitalized"
11-
>
6+
<ngx-masonry [options]="masonryOptions" [updateLayout]="updateMasonryLayout" [useImagesLoaded]="false" *ngIf="viewInitalized">
127
<div class="masonry-item-sizer"></div>
138
<div class="gutter-sizer"></div>
149
<div ngxMasonryItem class="masonry-item" *ngFor="let resource of resources">

src/app/web-portal/resources/containers/resource-details-shell/resource-details-shell.component.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { map } from 'rxjs/operators';
77
import { Resource } from '@shared/models/resource.model';
88

99
// NgRx
10-
import * as fromResources from '../../reducers';
11-
import { ResourceActions } from '../../actions';
10+
import * as fromResources from '@web-portal/resources/reducers';
11+
import { ResourceActions } from '@web-portal/resources/actions';
1212

1313
@Component({
1414
selector: 'app-resource-details-shell',
@@ -19,14 +19,15 @@ export class ResourceDetailsShellComponent implements OnDestroy {
1919
resource$: Observable<Resource>;
2020
actionsSubscription: Subscription;
2121

22-
constructor(route: ActivatedRoute, store: Store<fromResources.State>) {
22+
constructor(route: ActivatedRoute, private store: Store<fromResources.State>) {
2323
this.actionsSubscription = route.params
2424
.pipe(map(params => ResourceActions.selectResource({ id: params.id })))
2525
.subscribe(store);
2626
this.resource$ = store.pipe(select(fromResources.getCurrentResource));
2727
}
2828

2929
ngOnDestroy(): void {
30+
this.store.dispatch(ResourceActions.clearSelectedResource());
3031
this.actionsSubscription.unsubscribe();
3132
}
3233
}

src/app/web-portal/resources/containers/resources-list/resources-list.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,26 @@ export class ResourcesListComponent implements OnInit, OnDestroy {
2626
constructor(private loaderService: LoaderService, private store: Store<fromResources.State>) {
2727
this.resources$ = this.store.select(fromResources.getResources);
2828
this.store.select(fromResources.getResourcesNextPage).subscribe(x => (this.thereIsMore = x !== null));
29-
3029
this.loaderSubscription = this.store.select(fromResources.getResourcesIsFetching).subscribe(isFetching => {
3130
isFetching ? this.loaderService.show() : this.loaderService.hide();
3231
this.loading = isFetching;
3332
});
3433
}
3534

3635
ngOnInit() {
37-
this.store.dispatch(ResourcesActions.loadResources());
36+
this.store.dispatch(ResourcesActions.loadResources({ orderByDirection: 'desc', pageSize: 20 }));
3837
}
3938

4039
getNextResources() {
4140
if (this.loading || !this.thereIsMore) {
4241
return;
4342
}
4443

45-
this.store.dispatch(ResourcesActions.loadResources());
44+
this.store.dispatch(ResourcesActions.loadNextResources());
4645
}
4746

4847
ngOnDestroy() {
48+
this.store.dispatch(ResourcesActions.clearResources());
4949
this.loaderSubscription.unsubscribe();
5050
}
5151
}

src/app/web-portal/resources/effects/resources.effects.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Injectable } from '@angular/core';
2-
import { Observable, of } from 'rxjs';
3-
import { mergeMap, catchError, map, withLatestFrom, take } from 'rxjs/operators';
2+
import { Observable } from 'rxjs';
3+
import { mergeMap, withLatestFrom } from 'rxjs/operators';
44
import { Action, Store } from '@ngrx/store';
55
import { Actions, Effect, ofType } from '@ngrx/effects';
66

@@ -21,17 +21,31 @@ export class ResourcesEffects {
2121
@Effect()
2222
loadResources$: Observable<Action> = this.actions$.pipe(
2323
ofType(ResourcesActions.loadResources.type),
24+
mergeMap(async action => {
25+
try {
26+
const resources = await this.resourceService.query(action.pageSize, null, action.orderByDirection);
27+
return ResourcesApiActions.loadResourcesSuccess({ resources });
28+
} catch (err) {
29+
return ResourcesApiActions.loadResourcesFailure(err);
30+
}
31+
})
32+
);
33+
34+
@Effect()
35+
loadNextResources$: Observable<Action> = this.actions$.pipe(
36+
ofType(ResourcesActions.loadNextResources.type),
2437
withLatestFrom(this.store.select(fromResources.getResourcesState)),
25-
mergeMap(([, state]) => {
38+
mergeMap(async ([, state]) => {
2639
if (state.currentPage > 0 && !state.startAfter) {
2740
return null;
2841
}
2942

30-
return this.resourceService.query(state.pageSize, state.startAfter, state.orderByDirection).pipe(
31-
take(1),
32-
map(resources => ResourcesApiActions.loadResourcesSuccess({ resources })),
33-
catchError(err => of(ResourcesApiActions.loadResourcesFailure(err)))
34-
);
43+
try {
44+
const resources = await this.resourceService.query(state.pageSize, state.startAfter, state.orderByDirection);
45+
return ResourcesApiActions.loadResourcesSuccess({ resources });
46+
} catch (err) {
47+
return ResourcesApiActions.loadResourcesFailure(err);
48+
}
3549
})
3650
);
3751
}

src/app/web-portal/resources/reducers/resources.reducer.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Resource } from '@shared/models/resource.model';
2-
import { ResourcesApiActions } from '../actions';
2+
import { ResourcesApiActions, ResourcesActions } from '../actions';
33

44
export interface State {
55
entities: Resource[];
@@ -21,8 +21,30 @@ export const initialState: State = {
2121
pageSize: 5,
2222
};
2323

24-
export function reducer(state = initialState, action: ResourcesApiActions.ResourcesApiActionsUnion): State {
24+
export function reducer(
25+
state = initialState,
26+
action: ResourcesApiActions.ResourcesApiActionsUnion | ResourcesActions.ResourcesActionsUnion
27+
): State {
2528
switch (action.type) {
29+
case ResourcesActions.loadResources.type: {
30+
return {
31+
...state,
32+
isFetching: true,
33+
entities: [],
34+
startAfter: null,
35+
errorMessage: '',
36+
currentPage: 0,
37+
pageSize: action.pageSize,
38+
orderByDirection: action.orderByDirection,
39+
};
40+
}
41+
case ResourcesActions.loadNextResources.type: {
42+
return {
43+
...state,
44+
isFetching: true,
45+
errorMessage: '',
46+
};
47+
}
2648
case ResourcesApiActions.loadResourcesSuccess.type: {
2749
const startAfter =
2850
(action.resources || []).length === state.pageSize ? action.resources[state.pageSize - 1].dateTime : null;
@@ -41,6 +63,10 @@ export function reducer(state = initialState, action: ResourcesApiActions.Resour
4163
return { ...state, isFetching: false, errorMessage: action.errorMsg };
4264
}
4365

66+
case ResourcesActions.clearResources.type: {
67+
return { ...state, entities: [], currentPage: 0, pageSize: 0, startAfter: 0 };
68+
}
69+
4470
default:
4571
return state;
4672
}
Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,46 @@
11
import { Injectable } from '@angular/core';
2-
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
2+
import { AngularFirestore } from '@angular/fire/firestore';
33
import { Observable } from 'rxjs';
4-
import { mapItemWithId, mapArrayWithId } from '@core/rxjs/pipes';
4+
import { mapItemWithId } from '@core/rxjs/pipes';
55

66
import { Resource } from '@shared/models/resource.model';
77

88
@Injectable()
99
export class ResourceService {
10-
itemsCollection: AngularFirestoreCollection<any>;
10+
constructor(private db: AngularFirestore) {}
1111

12-
constructor(private db: AngularFirestore) {
13-
this.itemsCollection = this.db.collection<any>('resources');
14-
}
15-
16-
public query(
12+
public async query(
1713
pageSize: number,
1814
startAfter?: any,
1915
orderByDirection?: firebase.firestore.OrderByDirection
20-
): Observable<Resource[]> {
21-
// TODO reduce complexity
22-
return this.db
16+
): Promise<Resource[]> {
17+
const snapshotChanges = await this.db
2318
.collection<Resource>('resources', ref => {
19+
let query: firebase.firestore.CollectionReference | firebase.firestore.Query = ref;
20+
query = query.where('published', '==', true);
21+
query = query.limit(pageSize);
22+
if (orderByDirection) {
23+
query = query.orderBy('dateTime', orderByDirection);
24+
}
2425
if (startAfter) {
25-
if (orderByDirection) {
26-
return ref
27-
.where('published', '==', true)
28-
.orderBy('dateTime', orderByDirection)
29-
.startAfter(startAfter)
30-
.limit(pageSize);
31-
} else {
32-
return ref
33-
.where('published', '==', true)
34-
.startAfter(startAfter)
35-
.limit(pageSize);
36-
}
37-
} else {
38-
if (orderByDirection) {
39-
return ref
40-
.where('published', '==', true)
41-
.orderBy('dateTime', orderByDirection)
42-
.limit(pageSize);
43-
} else {
44-
return ref.where('published', '==', true).limit(pageSize);
45-
}
26+
query = query.startAfter(startAfter);
4627
}
28+
return query;
4729
})
48-
.snapshotChanges()
49-
.pipe(mapArrayWithId);
30+
.ref.get();
31+
32+
return this.mapQuerySnapshotToResource(snapshotChanges);
5033
}
5134

5235
public get(resourceId: string): Observable<Resource> {
53-
return this.itemsCollection
36+
return this.db
37+
.collection<Resource>('resources')
5438
.doc<Resource>(resourceId)
5539
.snapshotChanges()
5640
.pipe(mapItemWithId);
5741
}
42+
43+
public mapQuerySnapshotToResource(snapshotChanges: firebase.firestore.QuerySnapshot): Resource[] {
44+
return snapshotChanges.docs.map<Resource>(x => ({ id: x.id, ...(x.data() as Resource) }));
45+
}
5846
}

0 commit comments

Comments
 (0)