Skip to content

Commit 2ceab8f

Browse files
feat: introduce rtdb server-client caching
1 parent 14e78ec commit 2ceab8f

File tree

9 files changed

+53
-13
lines changed

9 files changed

+53
-13
lines changed

src/core/angularfire2.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export const FirebaseAppConfig = new InjectionToken<FirebaseOptions>('angularfir
1717
// Put in database.ts when we drop database-depreciated
1818
export const RealtimeDatabaseURL = new InjectionToken<string>('angularfire2.realtimeDatabaseURL');
1919

20+
export const UniversalDatabaseTransferStateKeyPrefix = new InjectionToken<string>('angularfire2.dbTransferStateKey');
21+
2022
export class FirebaseZoneScheduler {
2123
constructor(public zone: NgZone) {}
2224
schedule(...args: any[]): Subscription {

src/core/firebase.app.module.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { InjectionToken, NgZone, NgModule } from '@angular/core';
22

3-
import { FirebaseAppConfig, FirebaseAppName } from './angularfire2';
3+
import { FirebaseAppConfig, FirebaseAppName, UniversalDatabaseTransferStateKeyPrefix } from './angularfire2';
44

55
import firebase from '@firebase/app';
66
import { FirebaseApp as _FirebaseApp, FirebaseOptions } from '@firebase/app-types';
@@ -42,7 +42,8 @@ export class AngularFireModule {
4242
ngModule: AngularFireModule,
4343
providers: [
4444
{ provide: FirebaseAppConfig, useValue: config },
45-
{ provide: FirebaseAppName, useValue: appName }
45+
{ provide: FirebaseAppName, useValue: appName },
46+
{ provide: UniversalDatabaseTransferStateKeyPrefix, useValue: 'RTDB' }
4647
]
4748
}
4849
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { NgModule } from '@angular/core';
2+
import { AngularFireDatabase } from './database';
3+
import '@firebase/database';
4+
5+
@NgModule({
6+
providers: [ AngularFireDatabase ]
7+
})
8+
export class AngularFireDatabaseServerModule { }

src/database/database.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { NgModule } from '@angular/core';
22
import { AngularFireDatabase } from './database';
3+
import { TransferState } from '@angular/platform-browser';
34
import '@firebase/database';
45

56
@NgModule({
6-
providers: [ AngularFireDatabase ]
7+
providers: [ AngularFireDatabase, TransferState ]
78
})
89
export class AngularFireDatabaseModule { }

src/database/database.spec.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { FirebaseApp, FirebaseAppConfig, AngularFireModule, FirebaseAppName } fr
22
import { AngularFireDatabase, AngularFireDatabaseModule, RealtimeDatabaseURL } from 'angularfire2/database';
33
import { TestBed, inject } from '@angular/core/testing';
44
import { COMMON_CONFIG } from './test-config';
5-
import { NgZone } from '@angular/core';
5+
import { NgZone, ApplicationRef } from '@angular/core';
6+
import { TransferState } from '@angular/platform-browser';
67

78
// generate random string to test fidelity of naming
89
const FIREBASE_APP_NAME = (Math.random() + 1).toString(36).substring(7);
@@ -11,6 +12,8 @@ describe('AngularFireDatabase', () => {
1112
let app: FirebaseApp;
1213
let db: AngularFireDatabase;
1314
let zone: NgZone
15+
let ts: TransferState
16+
let appRef: ApplicationRef
1417

1518
beforeEach(() => {
1619
TestBed.configureTestingModule({
@@ -19,10 +22,13 @@ describe('AngularFireDatabase', () => {
1922
AngularFireDatabaseModule
2023
]
2124
});
22-
inject([FirebaseApp, AngularFireDatabase, NgZone], (app_: FirebaseApp, _db: AngularFireDatabase, _zone: NgZone) => {
25+
inject([FirebaseApp, AngularFireDatabase, NgZone, TransferState, ApplicationRef],
26+
(app_: FirebaseApp, _db: AngularFireDatabase, _zone: NgZone, _ts: TransferState, _appRef: ApplicationRef) => {
2327
app = app_;
2428
db = _db;
2529
zone = _zone;
30+
ts = _ts;
31+
appRef = _appRef
2632
})();
2733
});
2834

@@ -42,7 +48,7 @@ describe('AngularFireDatabase', () => {
4248
});
4349

4450
it('should accept a Firebase App in the constructor', () => {
45-
const __db = new AngularFireDatabase(app.options, app.name, null!, zone);
51+
const __db = new AngularFireDatabase(app.options, app.name, null!, 'RTDB', ts, appRef, zone);
4652
expect(__db instanceof AngularFireDatabase).toEqual(true);
4753
});
4854

src/database/database.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,41 @@
1-
import { Injectable, Inject, Optional, NgZone } from '@angular/core';
1+
import { Injectable, Inject, Optional, NgZone, ApplicationRef } from '@angular/core';
22
import { FirebaseDatabase } from '@firebase/database-types';
33
import { PathReference, DatabaseQuery, DatabaseReference, DatabaseSnapshot, ChildEvent, ListenEvent, QueryFn, AngularFireList, AngularFireObject } from './interfaces';
44
import { getRef } from './utils';
55
import { InjectionToken } from '@angular/core';
66
import { FirebaseOptions } from '@firebase/app-types';
77
import { createListReference } from './list/create-reference';
88
import { createObjectReference } from './object/create-reference';
9-
import { FirebaseAppConfig, FirebaseAppName, RealtimeDatabaseURL, _firebaseAppFactory, FirebaseZoneScheduler } from 'angularfire2';
9+
import { FirebaseAppConfig, FirebaseAppName, RealtimeDatabaseURL, UniversalDatabaseTransferStateKeyPrefix, _firebaseAppFactory, FirebaseZoneScheduler } from 'angularfire2';
10+
import { makeStateKey, TransferState } from '@angular/platform-browser';
11+
import 'rxjs/add/operator/take';
1012

1113
@Injectable()
1214
export class AngularFireDatabase {
1315
public readonly database: FirebaseDatabase;
1416
public readonly scheduler: FirebaseZoneScheduler;
17+
private readFromCache = true;
1518

1619
constructor(
1720
@Inject(FirebaseAppConfig) config:FirebaseOptions,
1821
@Optional() @Inject(FirebaseAppName) name:string,
1922
@Optional() @Inject(RealtimeDatabaseURL) databaseURL:string,
23+
@Inject(UniversalDatabaseTransferStateKeyPrefix) private cacheKeyPrefix:string,
24+
private ts: TransferState,
25+
appRef: ApplicationRef,
2026
zone: NgZone
2127
) {
2228
this.scheduler = new FirebaseZoneScheduler(zone);
2329
this.database = zone.runOutsideAngular(() => {
2430
const app = _firebaseAppFactory(config, name);
2531
return app.database(databaseURL || undefined);
2632
});
33+
appRef.isStable.filter(Boolean).take(1).subscribe(() => this.readFromCache = false)
2734
}
2835

2936
list<T>(pathOrRef: PathReference, queryFn?: QueryFn): AngularFireList<T> {
3037
const ref = getRef(this.database, pathOrRef);
38+
const ssrCached = this.readFromCache && this.getValueFromServerRequest<T>(pathOrRef.toString()) || undefined
3139
let query: DatabaseQuery = ref;
3240
if(queryFn) {
3341
query = queryFn(ref);
@@ -37,13 +45,21 @@ export class AngularFireDatabase {
3745

3846
object<T>(pathOrRef: PathReference): AngularFireObject<T> {
3947
const ref = getRef(this.database, pathOrRef);
40-
return createObjectReference<T>(ref, this);
48+
const ssrCached = this.readFromCache && this.getValueFromServerRequest<T>(pathOrRef.toString()) || undefined
49+
return createObjectReference<T>(ref, this, ssrCached);
4150
}
4251

4352
createPushId() {
4453
return this.database.ref().push().key;
4554
}
4655

56+
private getValueFromServerRequest<T>(path: string) {
57+
return this.ts.get<T | undefined>(this.getTsCacheKey(path), undefined);
58+
}
59+
60+
private getTsCacheKey(path: string) {
61+
return makeStateKey<string>(`${this.cacheKeyPrefix}.${path}`);
62+
}
4763
}
4864

4965
export {

src/database/list/create-reference.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { createDataOperationMethod } from './data-operation';
66
import { createRemoveMethod } from './remove';
77
import { AngularFireDatabase } from '../database';
88

9-
export function createListReference<T>(query: DatabaseQuery, afDatabase: AngularFireDatabase): AngularFireList<T> {
9+
export function createListReference<T>(query: DatabaseQuery, afDatabase: AngularFireDatabase, ssrCached?: T[]): AngularFireList<T> {
1010
return {
1111
query,
1212
update: createDataOperationMethod<Partial<T>>(query.ref, 'update'),
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { DatabaseQuery, AngularFireObject } from '../interfaces';
22
import { createObjectSnapshotChanges } from './snapshot-changes';
33
import { AngularFireDatabase } from '../database';
4+
import 'rxjs/add/operator/startWith';
45

5-
export function createObjectReference<T>(query: DatabaseQuery, afDatabase: AngularFireDatabase): AngularFireObject<T> {
6+
export function createObjectReference<T>(query: DatabaseQuery, afDatabase: AngularFireDatabase, ssrCached?: T): AngularFireObject<T> {
67
return {
78
query,
89
snapshotChanges<T>() {
@@ -12,10 +13,14 @@ export function createObjectReference<T>(query: DatabaseQuery, afDatabase: Angul
1213
update(data: Partial<T>) { return query.ref.update(data as any) as Promise<void>; },
1314
set(data: T) { return query.ref.set(data) as Promise<void>; },
1415
remove() { return query.ref.remove() as Promise<void>; },
15-
valueChanges<T>() {
16+
valueChanges<T>() {
1617
const snapshotChanges$ = createObjectSnapshotChanges(query)();
17-
return afDatabase.scheduler.keepUnstableUntilFirst(snapshotChanges$)
18+
const baseObs = afDatabase.scheduler.keepUnstableUntilFirst(snapshotChanges$)
1819
.map(action => action.payload.exists() ? action.payload.val() as T : null)
20+
21+
return ssrCached
22+
? baseObs.startWith(ssrCached as any) // typescript issue?
23+
: baseObs
1924
},
2025
}
2126
}

src/database/public_api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './list/state-changes';
66
export * from './list/audit-trail';
77
export * from './observable/fromRef';
88
export * from './database.module'
9+
export * from './database-server.module'

0 commit comments

Comments
 (0)