Skip to content

Adding more injector safety - #3590

Merged
jamesdaniels merged 4 commits into
angular:mainfrom
jamesdaniels:jamesdaniels_moreInjectorSafety
Dec 13, 2024
Merged

jamesdaniels merged 4 commits into
angular:mainfrom
jamesdaniels:jamesdaniels_moreInjectorSafety

Conversation

@jamesdaniels

@jamesdaniels jamesdaniels commented Dec 13, 2024 •

Copy link
Copy Markdown
Contributor
  • Zone wrapper noops for our other helpers
  • Add a warning / error on potential Zone / hydration issues
  • Pass injection context to zoneWrapFn
  • Pass injection context into the Promise wrapper
  • beforeAuthStateChanged should not block

@jamesdaniels
jamesdaniels merged commit 45ccd39 into angular:main Dec 13, 2024
@jamesdaniels
jamesdaniels deleted the jamesdaniels_moreInjectorSafety branch December 13, 2024 21:35
@davidgeary

Copy link
Copy Markdown

@jamesdaniels Just a heads up...

I've just installed RC4 and noticed the "Firebase API called outside injection context" warning in the console. The error message also includes a link to "Find out more" pointing to https://github.com/angular/angularfire/blob/main/docs/zones.md (see line 87 in zone.ts) but this file doesn't appear to exist at the moment.

@jamesdaniels

Copy link
Copy Markdown
Contributor Author

@davidgeary thanks yeah, getting that page together now. You can likely safely ignore the warnings, they're only produced in dev mode and are intended to help developers track down any change-detection / rehydration instabilities

@davidgeary

Copy link
Copy Markdown

@jamesdaniels Ah, thanks. I was just starting to work through my code to see where the problem was, so you've saved me some time there!

@ciriousjoker

Copy link
Copy Markdown

Apparently, once you await something inside an injection context, you lose the injection context.

Here's our workaround:

/**
 * Runs an async function in the injection context. This can be awaited, unlike @see {runInInjectionContext}.
 * For some ungodly reason, only the first awaited call inside the fn callback is actually inside the injection context.
 * After something is awaited, the context is lost.
 *
 * NOTE: Use this sparingly and only when absolutely necessary.
 * This is a band-aid solution for this issue:
 * https://github.com/angular/angularfire/pull/3590
 *
 * @param injector The injector, usually inject(EnvironmentInjector)
 * @param fn The async callback to be awaited
 */
export async function runAsyncInInjectionContext<T>(injector: Injector, fn: () => Promise<T>): Promise<T> {
  return await runInInjectionContext(injector, () => {
    return new Promise((resolve, reject) => {
      fn().then(resolve).catch(reject);
    });
  });
}

Usage:

await runAsyncInInjectionContext(this.injector, async () => {
  await loadBundle(this.firestore, response);
  await getDocs(); // <-- this no longer is inside context
});

@syamanashi

Copy link
Copy Markdown
Contributor

Apparently, once you await something inside an injection context, you lose the injection context.

Here's our workaround:

/**
 * Runs an async function in the injection context. This can be awaited, unlike @see {runInInjectionContext}.
 * For some ungodly reason, only the first awaited call inside the fn callback is actually inside the injection context.
 * After something is awaited, the context is lost.
 *
 * NOTE: Use this sparingly and only when absolutely necessary.
 * This is a band-aid solution for this issue:
 * https://github.com/angular/angularfire/pull/3590
 *
 * @param injector The injector, usually inject(EnvironmentInjector)
 * @param fn The async callback to be awaited
 */
export async function runAsyncInInjectionContext<T>(injector: Injector, fn: () => Promise<T>): Promise<T> {
  return await runInInjectionContext(injector, () => {
    return new Promise((resolve, reject) => {
      fn().then(resolve).catch(reject);
    });
  });
}

Usage:

await runAsyncInInjectionContext(this.injector, async () => {
  await loadBundle(this.firestore, response);
  await getDocs(); // <-- this no longer is inside context
});

This workaround worked for me to suppress the console warnings in Angular v20.0. with "@angular/fire": "^20.0.1",. I tried every other method I could find on any thread about the issue and this is the only thing that suppresses the warnings.

Is this work-around still the only option?

@sasos90

sasos90 commented Aug 22, 2025

Copy link
Copy Markdown

If i import the "getDocs" od "doc" or "collection" from "firebase/firestore" then i have no warnings. But if i use them from @angular/fire, then i get warnings. What is the solution here?
I use v19.2.0 of @angular/fire

armando-navarro added a commit that referenced this pull request Sep 27, 2026
…3770)

AngularFire wrapped beforeAuthStateChanged so that registering the
hook added a pending task, cleared only when the callback first runs.
Firebase runs that callback only on a sign-in or sign-out, so for a
visitor who does neither the app never became stable. Registered on
the server, it failed ng build during route extraction and left
server-rendered requests without a response.

This restores the blockUntilFirst: false override from #3590, which
#3613 dropped without comment while adding log-level overrides next
to it. The callback still runs inside Angular's zone and injection
context, and its returned promise still reaches Firebase, so a
rejection still cancels the sign-in. A call outside an injection
context now logs its per-call warning only at the verbose level, as
onMessage does.

Fixes #3748

docs(auth): scope the beforeAuthStateChanged note to rc.1 and earlier

Merging this change closes #3748, so the section's present-tense note
would point at a closed issue. Also removed the false claim that the
@angular/fire/auth import makes ng build hang: the guide registers the
hook only in the browser, so its own build succeeds.
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

Successfully merging this pull request may close these issues.

5 participants