angular/angularfire

Firebase APIs outside of an Injection context

itskaif07 opened this issue ยท 19 comments

I keep getting error about,

Calling Firebase APIs outside of an Injection context may destabilize your application leading to subtle change-detection and hydration bugs.

I did properly set up the angular firebase integration although signIn, logIn working properly, but this error won't resolve and it makes the authentication delay at each reload.

Appconfig.ts -

const firebaseConfig = {
apiKey: "",
authDomain: "
",
projectId: "",
storageBucket: "
",
messagingSenderId: "",
appId: "
"
}

export const appConfig: ApplicationConfig = {
providers:
[
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(),
provideFirebaseApp(() => initializeApp(firebaseConfig)),
provideAuth(() => getAuth()),
provideFirestore(() => getFirestore())
]
};

Service:

auth: Auth = inject(Auth)
fireStore: Firestore = inject(Firestore)
ngZone = inject(NgZone)

userSubject: BehaviorSubject = new BehaviorSubject(null)

private authSubscription;

constructor() {
this.authSubscription = this.auth.onAuthStateChanged(user => {
this.userSubject.next(user)
})
}

signUp(fullName: string, email: string, password: string, username: string, phone: string = '', address: string = '', pincode: string): Observable {
return this.ngZone.run(() => {

  const promise = createUserWithEmailAndPassword(this.auth, email, password)
    .then(response => {
      const userCredentials = response.user;
      if (userCredentials) {
        return sendEmailVerification(userCredentials)
          .then(() => {
            // Update profile with username
            return updateProfile(userCredentials, { displayName: username })
              .then(() => {
                // Save user details in Firestore
                return this.saveUserDetails(response.user.uid, fullName, address, phone, pincode);
              })
              .catch(error => {
                console.error("Error updating profile:", error);
                throw new Error('Profile update failed');
              });
          })
          .catch(error => {
            console.error("Error sending email verification:", error);
            throw new Error('Email verification failed');
          });
      } else {
        throw new Error('User credentials not found');
      }
    })
    .catch(error => {
      console.error("Error during signup:", error);
      throw error; // Propagate the error
    });

  return from(promise).pipe(
    map(() => { }),
    catchError(error => {
      console.error("Error in signup:", error);
      return throwError(() => new Error(error.message)); // Return an error observable
    })
  );
})

}

saveUserDetails(uid: string, fullName: string, address: string, phone: string, pincode: string): Promise {
const userRef = doc(this.fireStore, 'users', uid)

return setDoc(userRef, {
  fullName: fullName,
  address: address,
  phone: phone,
  pincode: pincode
}).then(() => {
  console.log("User details saved in Firestore.");
}).catch((error) => {
  console.error("Error saving user details in Firestore:", error);
  throw error;  // rethrow to propagate error in the stream
});

}

Component:

onSubmit() {
const rawForm = this.signUpForm.getRawValue();

this.loader.showLoader()
this.authService.signUp(rawForm.name,rawForm.email, rawForm.password, rawForm.username, rawForm.phone, rawForm.address, rawForm.pincode).subscribe((next) => {
  this.router.navigateByUrl('/email-ver')
  this.loader.hideLoader()
}, err => {
  console.log('Some error occurred during signup', err);
  this.loader.hideLoader()
})

}

This issue does not seem to follow the issue template. Make sure you provide all the required information.

I am getting the same error:
"Calling Firebase APIs outside of an Injection context may destabilize your application leading to subtle change-detection and hydration bugs. Find more at https://github.com/angular/angularfire/blob/main/docs/zones.md"
https://github.com/angular/angularfire/blob/main/docs/zones.md does not exist.
The error is coming from

function warnOutsideInjectionContext(original: any, operation: string) {

I am getting the same error: "Calling Firebase APIs outside of an Injection context may destabilize your application leading to subtle change-detection and hydration bugs. Find more at https://github.com/angular/angularfire/blob/main/docs/zones.md" https://github.com/angular/angularfire/blob/main/docs/zones.md does not exist. The error is coming from

angularfire/src/zones.ts

Line 82 in bc926a8

function warnOutsideInjectionContext(original: any, operation: string) {

Just read #3590 (comment)

Resolved using this #3590 (comment)

Resolved using this #3590 (comment)

not a solution

Resolved using this #3590 (comment)

not a solution

No it is not. Just a workaround for now.

Resolved using this #3590 (comment)

not a solution

No it is not. Just a workaround for now.

I implemented this workaround as a static function in a helper class called OGSWAngularFirestoreHelper

This worked for me for async calls:

  async retrieveDocument<T>(path: string): Promise<T | null> {
    return await OGSWAngularFirestoreHelper.runAsyncInInjectionContext(this.injector, async () => { //[WORKAROUND]
      this.logger.trace(`${this._className}::retrieveDocument`, `Retreiving: ${path}`);
      try {
        // Create a Firestore document reference and fetch the document snapshot
        const documentRef = doc(this.afs, path);
        const documentSnapshot: DocumentSnapshot<DocumentData> = await getDoc(documentRef);

        // Extract the document data, casting it to the expected type T
        const docObj = documentSnapshot.exists() ? (documentSnapshot.data() as T) : null;
        this.logger.trace(`${this._className}::retrieveDocument`, `docObj:`, docObj);

        if (!docObj) {
          this.logger.warn(`${this._className}::retrieveDocument`, `Document '${documentRef.id}' was empty.`);
          this.logger.trace(`${this._className}::retrieveDocument`, `Path: ${path}`);
        }
        return docObj;
      } catch (error) {
        const errorMessage: string = OGSWAngularFirestoreHelper.extractErrorMessage(error);
        this.logger.error(`${this._className}::retrieveDocument`, `Error retrieving Firebase document: ${errorMessage}`);
        throw errorMessage;
      }
    }); //[WORKAROUND]
  }

But this will not work for standard sync calls like this because the work around is async:

  documentObserver<T>(path: string): Observable<T | null> {
    this.logger.trace(`${this._className}::documentObserver`, `Retreiving: ${path}`);
    try {
      // Create a Firestore document reference and fetch the document snapshot
      const documentRef = doc(this.afs, path);
      //const documentSnapshot = docData(documentRef); <-- This would return Observable<T | undefined>
      //Return Observable<T | null>
      const documentSnapshot = docData(documentRef).pipe(
        map(data => (data) ? data as T : null)
      );
      return documentSnapshot as Observable<T | null>;
    } catch (error) {
      const errorMessage: string = OGSWAngularFirestoreHelper.extractErrorMessage(error);
      this.logger.error(`${this._className}::documentObserver`, `Error retrieving Firebase document: ${errorMessage}`);
      throw errorMessage;
    }
  }

So I created a sync version in my helper class:

static runSyncInInjectionContext<T>(injector: Injector, fn: () => T): T {
  return runInInjectionContext(injector, fn);
}

BTW, here is my class constructor:

  constructor(
    private afs: Firestore,
    private afstorage: Storage,
    private logger: NGXLogger,
    private injector: Injector //[WORKAROUND]
  ) {
    this._className = this.constructor.name;
    this.logger.trace(`${this._className}::constructor`, `Service Instantiated`);
  }

This is muddying up my logging. Any expected time for resolution?

I have come to the conclusion that this warning is not helpful and so I have used the documentation to silence the warnings.

import { LogLevel, setLogLevel } from '@angular/fire';
setLogLevel(LogLevel.SILENT);

The problem is finding a place to do so now that Angular doesn't expose a tests.ts file (because the safest thing is to start by turning these log messages off only during testing). I could have put it in main.ts, but since I need to call provideFirebaseApp in every test that might trigger the warnings and in production I put it in my provideOurFirebaseApp code file.

https://github.com/rgant/brainfry/blob/35b5607c2e9186f5a46ff7adfae486cd1737d8df/src/app/core/firebase-app.provider.ts#L17

A lot of people suggest this workaround
BUT is there a REAL solution?
OR is this a ZONE.js problem that will go away only once ZONE.js is gone from Angular?

I'm writing fully tested code using the Firebase Emulators. None of these warning have ever triggered a real actual test failure. So IMO, they are not helpful.

@rgant so those warnings are something like "false positives" and can be fully ignored?
If so, did the Angular team miss an edge case when implementing this functionality?
I need to be absolutely sure, since we do not want to push bad code into prod, do we :)

The team is one developer working part time, and possibly not even really on the Angular side of things. But he is a good developer and knows that the Angular injection context is important. So he wanted to give us warnings, but applied the warnings in a global way when it probably should be more specific. There are probably cases where the context matters; James has mentioned experiencing problems. I haven't found any actual documentation on those specific experiences so I cannot really evaluate that.

But I can comment on my code and it is working across a number of Firebase Authentication methods and with Firebase Storage and Firestore. Since I plan to test all of those cases, I don't feel that the warning is helpful to my case. If you aren't automated testing your code, then you might appreciate the reminders to consider the context of the methods.

I've never said that people on the angular team were incompetent :)
IMHO they're the dopest bunch.
A bit off topic, but what do you mean by "The team is one developer working part time" ?

@adamstret I was attempting to explain that this is a part-time-single-developer project. It is not fully supported by the whole Angular team; it is not fully supported by the whole Firebase team. It is one good developer working to connect Angular and Firebase for us. So we cannot expect robust support like core Angular or Firebase products.

If you look at the commit history you will see that there is primarily one developer working on this project, with some support from the user community. When James has time, generally around major releases, we get some updates. But outside of those times we are generally working with what we have.

Oh I see,
Anyways, as you suggested, I'll just ignore those warnings and keep up hope that Angular dumps zone.js in the near future.
Thanks.

you can vote : here

For some reason, when querying asynchronous data in Firestore, the context is lost in the service. It's easy to fix โ€” use runInInjectionContext() to recover the context and then return the value.
.....
someMethod(){

     runInInjectionContext(this.environmentInjector, () => {
           inject(Firestore); // Do what you need with the injected service
            this.posts$=collectionData(collection(this.firestore, 'posts')) as Observable<Post[]>;
      });
   return this.posts$;

}....
documentation link: https://angular.dev/guide/di/dependency-injection-context#run-within-an-injection-context

What is the status of this?