Skip to content

Master => v6 #2298

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jan 28, 2020
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
"private": true,
"scripts": {
"test": "npm run test:node",
"test:chrome": "npx firebase emulators:exec --project=angularfire2-test \"npx ng test --watch=false --browsers=Chrome\"",
"test:watch": "npx firebase emulators:exec --project=angularfire2-test \"npx ng test --watch=true --browsers=Chrome\"",
"test:chrome": "npx firebase emulators:exec --project=angularfire2-test \"npx ng test --watch=false --browsers=Chrome\"",
"test:chrome-headless": "npx firebase emulators:exec --project=angularfire2-test \"npx ng test --watch=false --browsers=ChromeHeadless\"",
"lint": "npx ng lint",
"test:node": "npx tsc -p tsconfig.jasmine.json; cp ./dist/packages-dist/schematics/versions.json ./dist/out-tsc/jasmine/schematics && npx firebase emulators:exec --project=angularfire2-test \"node -r tsconfig-paths/register ./tools/jasmine.js\"",
"test:typings": "node ./tools/run-typings-test.js",
"test:build": "bash ./test/ng-build/build.sh",
"test:universal": "cp -R dist/packages-dist test/universal-test/node_modules/angularfire2 && cd test/universal-test && npm run prerender",
"test:universal": "npm run build && cd test/universal-test && yarn && npm run prerender",
"test:all": "npm run test:node && npm run test:chrome-headless && npm run test:typings && npm run test:build",
"build": "tsc tools/build.ts; node ./tools/build.js",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1"
Expand Down Expand Up @@ -89,6 +90,7 @@
"pretty-size": "^2.0.0",
"protractor": "3.0.0",
"reflect-metadata": "0.1.2",
"replace-in-file": "^5.0.2",
"rimraf": "^2.5.4",
"schematics-utilities": "^2.0.1",
"shelljs": "^0.8.0",
Expand Down
10 changes: 6 additions & 4 deletions src/analytics/analytics.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Injectable, Optional, NgZone, OnDestroy, ComponentFactoryResolver, Inject, PLATFORM_ID, Injector, NgModuleFactory } from '@angular/core';
import { Subscription, from, Observable, of } from 'rxjs';
import { filter, withLatestFrom, switchMap, map, tap, pairwise, startWith, groupBy, mergeMap } from 'rxjs/operators';
import { filter, withLatestFrom, switchMap, map, tap, pairwise, startWith, groupBy, mergeMap, observeOn } from 'rxjs/operators';
import { Router, NavigationEnd, ActivationEnd, ROUTES } from '@angular/router';
import { ɵrunOutsideAngular } from '@angular/fire';
import { ɵAngularFireSchedulers } from '@angular/fire';
import { AngularFireAnalytics, DEBUG_MODE } from './analytics';
import { User } from 'firebase/app';
import { Title } from '@angular/platform-browser';
Expand Down Expand Up @@ -164,15 +164,17 @@ export class UserTrackingService implements OnDestroy {
zone: NgZone,
@Inject(PLATFORM_ID) platformId:Object
) {
const schedulers = new ɵAngularFireSchedulers(zone);

if (!isPlatformServer(platformId)) {
zone.runOutsideAngular(() => {
// @ts-ignore zap the import in the UMD
this.disposable = from(import('firebase/auth')).pipe(
observeOn(schedulers.outsideAngular),
switchMap(() => analytics.app),
map(app => app.auth()),
switchMap(auth => new Observable<User|null>(auth.onAuthStateChanged.bind(auth))),
switchMap(user => analytics.setUserId(user ? user.uid : null!)),
ɵrunOutsideAngular(zone)
switchMap(user => analytics.setUserId(user ? user.uid : null!))
).subscribe();
});
}
Expand Down
15 changes: 8 additions & 7 deletions src/analytics/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Injectable, Inject, Optional, NgZone, InjectionToken, PLATFORM_ID } from '@angular/core';
import { of, empty, throwError } from 'rxjs';
import { of, empty } from 'rxjs';
import { isPlatformBrowser } from '@angular/common';
import { map, tap, shareReplay, switchMap, catchError } from 'rxjs/operators';
import { FirebaseAppConfig, FirebaseOptions, ɵrunOutsideAngular, ɵlazySDKProxy, FIREBASE_OPTIONS, FIREBASE_APP_NAME, ɵfirebaseAppFactory, ɵPromiseProxy } from '@angular/fire';
import { map, tap, shareReplay, switchMap, observeOn } from 'rxjs/operators';
import { FirebaseAppConfig, FirebaseOptions, ɵAngularFireSchedulers, ɵlazySDKProxy, FIREBASE_OPTIONS, FIREBASE_APP_NAME, ɵfirebaseAppFactory, ɵPromiseProxy } from '@angular/fire';
import { analytics } from 'firebase';

export interface Config {[key:string]: any};
Expand Down Expand Up @@ -48,6 +48,8 @@ export class AngularFireAnalytics {
zone: NgZone
) {

const schedulers = new ɵAngularFireSchedulers(zone);

if (isPlatformBrowser(platformId)) {

window[DATA_LAYER_NAME] = window[DATA_LAYER_NAME] || [];
Expand All @@ -74,19 +76,18 @@ export class AngularFireAnalytics {
if (debugModeEnabled) { this.updateConfig({ [DEBUG_MODE_KEY]: 1 }) }

const analytics = of(undefined).pipe(
switchMap(() => zone.runOutsideAngular(() => import('firebase/analytics'))),
catchError(err => err.message === 'Not supported' ? empty() : throwError(err) ),
observeOn(schedulers.outsideAngular),
switchMap(() => isPlatformBrowser(platformId) ? import('firebase/analytics') : empty()),
map(() => ɵfirebaseAppFactory(options, zone, nameOrConfig)),
map(app => app.analytics()),
tap(analytics => {
if (analyticsCollectionEnabled === false) { analytics.setAnalyticsCollectionEnabled(false) }
}),
ɵrunOutsideAngular(zone),
shareReplay({ bufferSize: 1, refCount: false }),
);

return ɵlazySDKProxy(this, analytics, zone);

}

}
19 changes: 10 additions & 9 deletions src/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Injectable, Inject, Optional, NgZone, PLATFORM_ID } from '@angular/core';
import { Observable, of, from } from 'rxjs';
import { switchMap, shareReplay, map } from 'rxjs/operators';
import { FIREBASE_OPTIONS, FIREBASE_APP_NAME, FirebaseOptions, FirebaseAppConfig, ɵfirebaseAppFactory, ɵFirebaseZoneScheduler, ɵrunOutsideAngular, ɵPromiseProxy, ɵlazySDKProxy } from '@angular/fire';
import { switchMap, map, observeOn, shareReplay } from 'rxjs/operators';
import { FIREBASE_OPTIONS, FIREBASE_APP_NAME, FirebaseOptions, FirebaseAppConfig, ɵPromiseProxy, ɵlazySDKProxy, ɵfirebaseAppFactory, ɵAngularFireSchedulers, ɵkeepUnstableUntilFirstFactory } from '@angular/fire';
import { User, auth } from 'firebase/app';

export interface AngularFireAuth extends ɵPromiseProxy<auth.Auth> {};
Expand Down Expand Up @@ -37,28 +37,29 @@ export class AngularFireAuth {
@Inject(FIREBASE_OPTIONS) options:FirebaseOptions,
@Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig:string|FirebaseAppConfig|null|undefined,
@Inject(PLATFORM_ID) platformId: Object,
private zone: NgZone
zone: NgZone
) {
const scheduler = new ɵFirebaseZoneScheduler(zone, platformId);
const schedulers = new ɵAngularFireSchedulers(zone);
const keepUnstableUntilFirst = ɵkeepUnstableUntilFirstFactory(schedulers, platformId);

const auth = of(undefined).pipe(
observeOn(schedulers.outsideAngular),
switchMap(() => zone.runOutsideAngular(() => import('firebase/auth'))),
map(() => ɵfirebaseAppFactory(options, zone, nameOrConfig)),
map(app => app.auth()),
ɵrunOutsideAngular(zone),
shareReplay({ bufferSize: 1, refCount: false }),
);

this.authState = auth.pipe(
observeOn(schedulers.outsideAngular),
switchMap(auth => from(auth.onAuthStateChanged)),
ɵrunOutsideAngular(zone),
scheduler.keepUnstableUntilFirst.bind(scheduler),
keepUnstableUntilFirst
);

this.user = auth.pipe(
observeOn(schedulers.outsideAngular),
switchMap(auth => from(auth.onIdTokenChanged)),
ɵrunOutsideAngular(zone),
scheduler.keepUnstableUntilFirst.bind(scheduler),
keepUnstableUntilFirst
);

this.idToken = this.user.pipe(
Expand Down
199 changes: 196 additions & 3 deletions src/core/angularfire2.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { TestBed, inject } from '@angular/core/testing';
import { PlatformRef, NgModule, CompilerFactory } from '@angular/core';
import { PlatformRef, NgModule, CompilerFactory, NgZone } from '@angular/core';
import { FirebaseApp, AngularFireModule } from './public_api';
import { Subscription } from 'rxjs';
import { Subscription, Observable, Subject, of } from 'rxjs';
import { COMMON_CONFIG } from '../test-config';
import { BrowserModule } from '@angular/platform-browser';
import { database } from 'firebase/app';
import 'firebase/database';
import { ɵZoneScheduler, ɵkeepUnstableUntilFirstFactory, ɵAngularFireSchedulers } from './angularfire2';
import { ɵPLATFORM_BROWSER_ID, ɵPLATFORM_SERVER_ID } from '@angular/common';
import { tap } from 'rxjs/operators';
import { TestScheduler } from 'rxjs/testing';

describe('angularfire', () => {
let subscription:Subscription;
Expand Down Expand Up @@ -41,6 +44,196 @@ describe('angularfire', () => {
app.delete().then(done, done.fail);
});

describe('ZoneScheduler', () => {
it('should execute the scheduled work inside the specified zone', done => {
let ngZone = Zone.current.fork({
name: 'ngZone'
});
const rootZone = Zone.current;

// Mimic real behavior: Executing in Angular
ngZone.run(() => {
const outsideAngularScheduler = new ɵZoneScheduler(rootZone);
outsideAngularScheduler.schedule(() => {
expect(Zone.current.name).not.toEqual('ngZone');
done();
});
});
});

it('should execute nested scheduled work inside the specified zone', done => {
const testScheduler = new TestScheduler(null!);
testScheduler.run(helpers => {
const outsideAngularScheduler = new ɵZoneScheduler(Zone.current, testScheduler);

let ngZone = Zone.current.fork({
name: 'ngZone'
});

let callbacksRan = 0;

// Mimic real behavior: Executing in Angular
ngZone.run(() => {
outsideAngularScheduler.schedule(() => {
callbacksRan++;
expect(Zone.current.name).not.toEqual('ngZone');

ngZone.run(() => {
// Sync queueing
outsideAngularScheduler.schedule(() => {
callbacksRan++;
expect(Zone.current.name).not.toEqual('ngZone');
});

// Async (10ms delay) nested scheduling
outsideAngularScheduler.schedule(() => {
callbacksRan++;
expect(Zone.current.name).not.toEqual('ngZone');
}, 10);

// Simulate flush from inside angular-
helpers.flush();
done();
expect(callbacksRan).toEqual(3);
})
});
helpers.flush();
});
});
})
})

describe('keepUnstableUntilFirstFactory', () => {
let schedulers: ɵAngularFireSchedulers;
let outsideZone: Zone;
let insideZone: Zone;
beforeAll(() => {
outsideZone = Zone.current;
insideZone = Zone.current.fork({
name: 'ngZone'
});
const ngZone = {
run: insideZone.run.bind(insideZone),
runGuarded: insideZone.runGuarded.bind(insideZone),
runOutsideAngular: outsideZone.runGuarded.bind(outsideZone),
runTask: insideZone.run.bind(insideZone)
} as NgZone;
schedulers = new ɵAngularFireSchedulers(ngZone);
})

it('should re-schedule emissions asynchronously', done => {
const keepUnstableOp = ɵkeepUnstableUntilFirstFactory(schedulers, ɵPLATFORM_SERVER_ID);

let ran = false;
of(null).pipe(
keepUnstableOp,
tap(() => ran = true)
).subscribe(() => {
expect(ran).toEqual(true);
done();
}, () => fail("Should not error"));

expect(ran).toEqual(false);
});

[ɵPLATFORM_SERVER_ID, ɵPLATFORM_BROWSER_ID].map(platformId =>
it(`should subscribe outside angular and observe inside angular (${platformId})`, done => {
const keepUnstableOp = ɵkeepUnstableUntilFirstFactory(schedulers, platformId);

insideZone.run(() => {
new Observable(s => {
expect(Zone.current).toEqual(outsideZone);
s.next("test");
}).pipe(
keepUnstableOp,
tap(() => {
expect(Zone.current).toEqual(insideZone);
})
).subscribe(() => {
expect(Zone.current).toEqual(insideZone);
done();
}, err => {
fail(err);
});
});
})
);

it('should block until first emission on server platform', done => {
const testScheduler = new TestScheduler(null!);
testScheduler.run(helpers => {
const outsideZone = Zone.current;
const taskTrack = new Zone['TaskTrackingZoneSpec']();
const insideZone = Zone.current.fork(taskTrack);
const trackingSchedulers: ɵAngularFireSchedulers = {
ngZone: {
run: insideZone.run.bind(insideZone),
runGuarded: insideZone.runGuarded.bind(insideZone),
runOutsideAngular: outsideZone.runGuarded.bind(outsideZone),
runTask: insideZone.run.bind(insideZone)
} as NgZone,
outsideAngular: new ɵZoneScheduler(outsideZone, testScheduler),
insideAngular: new ɵZoneScheduler(insideZone, testScheduler),
};
const keepUnstableOp = ɵkeepUnstableUntilFirstFactory(trackingSchedulers, ɵPLATFORM_SERVER_ID);

const s = new Subject();
s.pipe(
keepUnstableOp,
).subscribe(() => { }, err => { fail(err); }, () => { });

// Flush to ensure all async scheduled functions are run
helpers.flush();
// Should now be blocked until first item arrives
expect(taskTrack.macroTasks.length).toBe(1);
expect(taskTrack.macroTasks[0].source).toBe('firebaseZoneBlock');

// Emit next item
s.next(123);
helpers.flush();

// Should not be blocked after first item
expect(taskTrack.macroTasks.length).toBe(0);

done();
});
})

it('should not block on client platform', done => {
const testScheduler = new TestScheduler(null!);
testScheduler.run(helpers => {
const outsideZone = Zone.current;
const taskTrack = new Zone['TaskTrackingZoneSpec']();
const insideZone = Zone.current.fork(taskTrack);
const trackingSchedulers: ɵAngularFireSchedulers = {
ngZone: {
run: insideZone.run.bind(insideZone),
runGuarded: insideZone.runGuarded.bind(insideZone),
runOutsideAngular: outsideZone.runGuarded.bind(outsideZone),
runTask: insideZone.run.bind(insideZone)
} as NgZone,
outsideAngular: new ɵZoneScheduler(outsideZone, testScheduler),
insideAngular: new ɵZoneScheduler(insideZone, testScheduler),
};
const keepUnstableOp = ɵkeepUnstableUntilFirstFactory(trackingSchedulers, ɵPLATFORM_BROWSER_ID);

const s = new Subject();
s.pipe(
keepUnstableOp,
).subscribe(() => { }, err => { fail(err); }, () => { });

// Flush to ensure all async scheduled functions are run
helpers.flush();

// Zone should not be blocked
expect(taskTrack.macroTasks.length).toBe(0);
expect(taskTrack.microTasks.length).toBe(0);

done();
});
})
})

describe('FirebaseApp', () => {

it('should provide a FirebaseApp for the FirebaseApp binding', () => {
Expand Down
Loading