Skip to content
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

Firebase APIs outside of an Injection context #3607

Open
itskaif07 opened this issue Jan 7, 2025 · 15 comments
Open

Firebase APIs outside of an Injection context #3607

itskaif07 opened this issue Jan 7, 2025 · 15 comments

Comments

@itskaif07
Copy link

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()
})

}

@google-oss-bot
Copy link

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

@unimodern
Copy link

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) {

@unimodern
Copy link

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)

@unimodern
Copy link

Resolved using this #3590 (comment)

@EthanSK
Copy link
Contributor

EthanSK commented Jan 11, 2025

Resolved using this #3590 (comment)

not a solution

@unimodern
Copy link

Resolved using this #3590 (comment)

not a solution

No it is not. Just a workaround for now.

@Steve-from-Memphis
Copy link

Steve-from-Memphis commented Jan 26, 2025

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?

@rgant
Copy link

rgant commented Feb 15, 2025

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

@adamstret
Copy link

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?

@rgant
Copy link

rgant commented Feb 16, 2025

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.

@adamstret
Copy link

adamstret commented Feb 16, 2025

@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 :)

@rgant
Copy link

rgant commented Feb 16, 2025

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.

@adamstret
Copy link

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" ?

@rgant
Copy link

rgant commented Feb 16, 2025

@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.

@adamstret
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants