Angularfire: Change Detection is broken

Created on 11 May 2020  ·  6Comments  ·  Source: angular/angularfire

Version info

Angular:
9.1.4

Firebase:
7.14.2

AngularFire:
6.0.0

I have a UsersService which fetches users over https callable functions.
Subscribing to the observable returned breaks the change detection in my component.

Changing the changeDetection to ChangeDetectionStrategy.OnPush and explicitly calling ChangeDetectorRef.markForCheck() and ApplicationRef.tick() or running the subscribe block in an NgZone fixes the problem.

Steps to set up and reproduce

class MyService {
  constructor(protected firebaseFunctions: AngularFireFunctions) {}

  fetch() {
    const f = this.firebaseFunctions.httpsCallable("myFunc");
    return f({});
  }
}
class MyComponent implements OnInit {
  res: any;

  constructor(private service: MyService, private zone: NgZone) {}

  ngOnInit() {
    this.service.fetch().subscribe((res) => {
      this.res = res;

      // This works:
      // this.zone.run(() => {
      //   this.res = res;
      // });
    });


  }

}

Expected behavior

Shouldnt break change detection or explicitly state how to work around it in docs.

Actual behavior

Does not work.

Most helpful comment

I solved this problem with this code
```typescript
Return from(f({}).toPromise())
````

All 6 comments

Same issue over here..

same here

Thanks to the workaround in #2444 I was able to fixe the auth guard.

For functions ive just copied the original source and changed outsideAngular into insideAngular and overrode the DI token.

import {Inject, Injectable, NgZone, Optional} from '@angular/core';
import {from, Observable, of} from 'rxjs';
import {map, observeOn, shareReplay, switchMap, tap} from 'rxjs/operators';
import {
    FIREBASE_APP_NAME,
    FIREBASE_OPTIONS,
    FirebaseAppConfig,
    FirebaseOptions,
    ɵAngularFireSchedulers,
    ɵfirebaseAppFactory,
    ɵlazySDKProxy
} from '@angular/fire';
import {ORIGIN, REGION} from '@angular/fire/functions';

@Injectable({
    providedIn: 'any'
})
export class FirebaseFunctions {

    public readonly httpsCallable: <T=any, R=any>(name: string) => (data: T) => Observable<R>

    constructor(
        @Inject(FIREBASE_OPTIONS) options:FirebaseOptions,
        @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig:string|FirebaseAppConfig|null|undefined,
        zone: NgZone,
        @Optional() @Inject(REGION) region:string|null,
        @Optional() @Inject(ORIGIN) origin:string|null
    ) {
        const schedulers = new ɵAngularFireSchedulers(zone);

        const functions = of(undefined).pipe(
            observeOn(schedulers.insideAngular),
            switchMap(() => import('firebase/functions')),
            map(() => ɵfirebaseAppFactory(options, zone, nameOrConfig)),
            map(app => app.functions(region || undefined)),
            tap(functions => {
                if (origin) { functions.useFunctionsEmulator(origin) }
            }),
            shareReplay({ bufferSize: 1, refCount: false }),
        );

        this.httpsCallable = <T=any, R=any>(name: string) =>
            (data: T) => from(functions).pipe(
                observeOn(schedulers.insideAngular),
                switchMap(functions => functions.httpsCallable(name)(data)),
                map(r => r.data as R)
            )

        return ɵlazySDKProxy(this, functions, zone);
    }
}

And in my module:

...
providers: [
  {
            provide: AngularFireFunctions,
            useClass: FirebaseFunctions
   }
],
...

I solved this problem with this code
```typescript
Return from(f({}).toPromise())
````

There's still a team working on it? The latest updates are full of bugs like this.

Saw somewhere else that there is already a fix for future 6.0.3

Thanks to the workaround in #2444 I was able to fixe the auth guard.

For functions ive just copied the original source and changed outsideAngular into insideAngular and overrode the DI token.

import {Inject, Injectable, NgZone, Optional} from '@angular/core';
import {from, Observable, of} from 'rxjs';
import {map, observeOn, shareReplay, switchMap, tap} from 'rxjs/operators';
import {
    FIREBASE_APP_NAME,
    FIREBASE_OPTIONS,
    FirebaseAppConfig,
    FirebaseOptions,
    ɵAngularFireSchedulers,
    ɵfirebaseAppFactory,
    ɵlazySDKProxy
} from '@angular/fire';
import {ORIGIN, REGION} from '@angular/fire/functions';

@Injectable({
    providedIn: 'any'
})
export class FirebaseFunctions {

    public readonly httpsCallable: <T=any, R=any>(name: string) => (data: T) => Observable<R>

    constructor(
        @Inject(FIREBASE_OPTIONS) options:FirebaseOptions,
        @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig:string|FirebaseAppConfig|null|undefined,
        zone: NgZone,
        @Optional() @Inject(REGION) region:string|null,
        @Optional() @Inject(ORIGIN) origin:string|null
    ) {
        const schedulers = new ɵAngularFireSchedulers(zone);

        const functions = of(undefined).pipe(
            observeOn(schedulers.insideAngular),
            switchMap(() => import('firebase/functions')),
            map(() => ɵfirebaseAppFactory(options, zone, nameOrConfig)),
            map(app => app.functions(region || undefined)),
            tap(functions => {
                if (origin) { functions.useFunctionsEmulator(origin) }
            }),
            shareReplay({ bufferSize: 1, refCount: false }),
        );

        this.httpsCallable = <T=any, R=any>(name: string) =>
            (data: T) => from(functions).pipe(
                observeOn(schedulers.insideAngular),
                switchMap(functions => functions.httpsCallable(name)(data)),
                map(r => r.data as R)
            )

        return ɵlazySDKProxy(this, functions, zone);
    }
}

And in my module:

...
providers: [
  {
            provide: AngularFireFunctions,
            useClass: FirebaseFunctions
   }
],
...

Great, this worked for me! I had to make a wrapper service in root module though - didn't work directly in lazyloaded child module.

I solved this problem with this code

Return from(f({}).toPromise())

Could only get this to work "once" for each promise, and it's a mess if you're combining different observables.

Was this page helpful?
0 / 5 - 0 ratings