angularfire icon indicating copy to clipboard operation
angularfire copied to clipboard

Inject() must be called from an injection context such as a constructor in AngularFirestoreDoc after upgrading to Angular v19

Open djamn opened this issue 10 months ago • 23 comments

I recently upgraded all dependencies to Angular v19 (from v18). I was previously on Angular v18 where everything worked fine. However, now, my whole console is spammed with the following error:

ERROR RuntimeError: NG0203: inject() must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`. Find more at https://angular.dev/errors/NG0203
    at injectInjectorOnly (core.mjs:1104:11)
    at ɵɵinject (core.mjs:1114:40)
    at inject (core.mjs:1199:10)
    at <instance_members_initializer> (angular-fire-compat-firestore.mjs:93:14)
    at new AngularFirestoreDocument (angular-fire-compat-firestore.mjs:98:3)
    at _AngularFirestore.doc (angular-fire-compat-firestore.mjs:621:12)
    at auth.service.ts:38:14
    at switchMap.js:16:17
    at OperatorSubscriber2._this._next (OperatorSubscriber.js:14:9)
    at Subscriber2.next (Subscriber.js:32:12)

However, my auth service at line 38:14 only has the following code: .doc<User>(`users/${user.uid}`)

The whole constructor looks like this (it is a angular service class)

  providedIn: 'root',
})
export class AuthService {
  isLoggedIn$: Observable<boolean>;
  /** User data of database */
  user$: Observable<User | null | undefined>;
  userRoles$: Observable<string[]>;
  userId: string | undefined = undefined;
  username: string | undefined = undefined;

  constructor(
    readonly firestore: AngularFirestore,
    readonly fireAuth: AngularFireAuth,
    readonly router: Router,
  ) {
    this.user$ = this.fireAuth.authState.pipe(
      switchMap((user) => {
        if (user) {
          this.userId = user.uid;
          return this.firestore
            .doc<User>(`users/${user.uid}`)
            .valueChanges()
            .pipe(
              map((userData) => {
                if (userData) {
                  this.username = userData.username; // Assign username
                }
                return userData;
              }),
            );
        } else {
          this.userId = undefined;
          this.username = undefined;
          return new Observable<User | null>((observer) => observer.next(null));
        }
      }),
    );

But I dont really know, why it does not work anymore in angular 19.

My package dependencies:

 "dependencies": {
    "@angular/animations": "^19.1.5",
    "@angular/cdk": "^19.1.3",
    "@angular/common": "^19.1.5",
    "@angular/compiler": "^19.1.5",
    "@angular/core": "^19.1.5",
    "@angular/fire": "^19.0.0",
    "@angular/forms": "^19.1.5",
    "@angular/material": "^19.1.3",
    "@angular/platform-browser": "^19.1.5",
    "@angular/platform-browser-dynamic": "^19.1.5",
    "@angular/router": "^19.1.5",
    "@fortawesome/angular-fontawesome": "^1.0.0",
    "@fortawesome/free-brands-svg-icons": "^6.7.1",
    "@fortawesome/free-regular-svg-icons": "^6.7.1",
    "@fortawesome/free-solid-svg-icons": "^6.7.1",
    "@ng-select/ng-select": "^14.2.2",
    "@ngx-translate/core": "^15.0.0",
    "@ngx-translate/http-loader": "^8.0.0",
    "angular-build-info": "^2.0.1",
    "canvas-confetti": "^1.9.3",
    "flowbite": "^2.5.2",
    "ng-recaptcha-2": "^15.0.2",
    "ngx-editor": "^18.0.0",
    "ngx-quill": "^27.0.0",
    "quill": "^2.0.3",
    "quill2-emoji": "^0.1.2",
    "rxjs": "~7.8.0",
    "tslib": "^2.8.1",
    "tw-elements": "^2.0.0",
    "zone.js": "^0.15.0"
  },

I also tried to set "preserveSymlinks": false, in angular.json however, the issue still persists.

djamn avatar Feb 12 '25 15:02 djamn

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

google-oss-bot avatar Feb 12 '25 15:02 google-oss-bot

Hi,

I have exactly the same issue. I pass through by using following code await runInInjectionContext(this.injector, async () => { but I think it's a monkey patch that do not resolve the source. Just want to know if there's a new way to implement or a real issue?

Thank you !

FidelNguyen avatar Feb 12 '25 18:02 FidelNguyen

I am having the same issue with version 19.0.0, it worked with the rc.0 release candidate of version 19 before though.

I have confirmed that version 19.0.0-rc.0 is the latest version without this issue, is there some new way to implement this that is not documented or is this a real issue?

codeneobee avatar Feb 17 '25 15:02 codeneobee

I believe that if you switch from the namespace syntax to the modular style that will solve the issue. https://firebase.google.com/docs/web/modular-upgrade

rgant avatar Feb 17 '25 19:02 rgant

I believe that if you switch from the namespace syntax to the modular style that will solve the issue. https://firebase.google.com/docs/web/modular-upgrade

I hope there is still another solution, since my project is huge with multiple different services

djamn avatar Feb 19 '25 10:02 djamn

You could wrap the modular methods in an Angular Service. Still a large change, but mostly just renaming things hopefully. I can describe more if interested.

The only other solution is to wrap every Firebase method in an injection context.

Or, stick with older versions.

rgant avatar Feb 19 '25 12:02 rgant

You could wrap the modular methods in an Angular Service. Still a large change, but mostly just renaming things hopefully. I can describe more if interested.

Would appreciate it if you could provide more information!

djamn avatar Feb 20 '25 11:02 djamn

Something like:

import {
  EnvironmentInjector,
  inject,
  Injectable,
  runInInjectionContext,
} from '@angular/core';
import { doc, Firestore } from '@angular/fire/firestore';
import type { DocumentReference } from '@angular/fire/firestore';

@Injectable({ providedIn: 'root' })
export class AngularFirestoreService {
  private readonly _firestore: Firestore = inject(Firestore);
  private readonly _injector: EnvironmentInjector = inject(EnvironmentInjector);

  /**
   * Note that the doc method could accept a CollectionReference or DocumentReference in addition to
   * Firestore.
   */
  public doc(path: string, ...pathSegments: string[]): DocumentReference {
    return runInInjectionContext(
      this._injector,
      (): DocumentReference => doc(this._firestore, path, ...pathSegments),
    );
  }
}

But note that I didn't test this code, it is off the cuff at 07:30 in the morning for me so it might be terrible.

rgant avatar Feb 20 '25 12:02 rgant

So it works when migrating namespace syntax to modular syntax. In my case it would be the following:

  private auth: Auth;
  private firestore: Firestore;
  isLoggedIn$: Observable<boolean>;
  user$: Observable<User | null | undefined>;
  userRoles$: Observable<string[]>;
  userId: string | undefined = undefined;
  username: string | undefined = undefined;

  constructor(firestore: Firestore, private router: Router) {
    this.auth = getAuth();
    this.firestore = firestore;

    this.user$ = new Observable<User | null | undefined>((observer) => {
      onAuthStateChanged(this.auth, async (user) => {
        if (user) {
          this.userId = user.uid;
          const userDocRef = doc(this.firestore, `users/${user.uid}`);
          docData(userDocRef).subscribe((userData) => {
            if (userData) {
              this.username = (userData as User).username;
            }
            observer.next(userData as User | null);
          });
        } else {
          this.userId = undefined;
          this.username = undefined;
          observer.next(null);
        }
      });
    });

However, this is still a very breaking change to all services and I did not find anything in the changelog.

djamn avatar Feb 23 '25 16:02 djamn

I think it's really important not to loose sight of this issue. We are stuck not able to upgrade to Angular 19 because of this issue. We're not ready to move off the compat API yet.

Regardless, the v19 release of this library says that it supports the compat API when in fact that just seems broken. If the library is not willing to fix the issue with the compat API, than it needs to remove support for it altogether and not gaslight the community.

kevin-induro avatar May 13 '25 19:05 kevin-induro

Now Angular V20 is out wondering what issues will come about

JGSolutions avatar May 29 '25 11:05 JGSolutions

I lost whole 1 day, figuring out it simply doesn't work. But as far as I read somewhere, it should be supported. So we can not move to Angular 19, without migrating everything to modules SDK, period.

sasos90 avatar May 30 '25 09:05 sasos90

Hello I am in the same position. I am moving from angular 16 to 19 and it seems this version of angular fire compat gives this above error. Our codebase is quite large and a move off of compat sdk would be timely. Is there any update or solution to this problem? Seems like the solutions in here are either use RunInInjectionContext or move to modular. Neither of which is clean.

vkpatel007 avatar Jun 23 '25 02:06 vkpatel007

This worked for me. I wasted way too much time on this. https://github.com/angular/angularfire/pull/3590#issuecomment-2581455741

syamanashi avatar Jul 05 '25 20:07 syamanashi

I'm also currently facing this issue during unit testing (Karma/Jasmine) and it seems that this problem only happens for Angular 19.

In Angular 20, this problem doesn't seem to occur.

astridsoraya avatar Jul 15 '25 13:07 astridsoraya

This ussue still occurs for Angular version 20.1 and @angular/fire version 20.0.1

tscislo avatar Jul 21 '25 09:07 tscislo

Finally I had to migrate my project to the new modular API, which works fine with ng 20.1. But still I should not be required to do this.

tscislo avatar Jul 23 '25 12:07 tscislo

How painful was it @tscislo ? Did you need to refactor a lot?

sasos90 avatar Jul 25 '25 11:07 sasos90

@sasos90 it wasn't very painful, but I had to refactor all use cases of angular/fire API. So it depends on your code base, mine wasn't that big. Good thing is that I was able to replace all use cases of the old API with the new modular one, so there were no gaps in functionality.

tscislo avatar Jul 25 '25 12:07 tscislo

@sasos90 it wasn't very painful, but I had to refactor all use cases of angular/fire API. So it depends on your code base, mine wasn't that big. Good thing is that I was able to replace all use cases of the old API with the new modular one, so there were no gaps in functionality.

Would you mind sharing an example of the refactoring into the the new modular API call? Just want to make sure I'm on the same page. Thank you!

syamanashi avatar Jul 25 '25 13:07 syamanashi

Moving to modular was mostly fine for me. But Analytics was a pain. Basically all custom features got removed. Might as well just use straight firebase a this point.

cm-cm-cm-cm avatar Jul 30 '25 14:07 cm-cm-cm-cm

@syamanashi Basically you need to get rid of everything that comes from @angular/fire/compat namespace with equivalents. I didn't use Analytics just data storage and mutation capabilities and it was fine for me.

tscislo avatar Jul 30 '25 15:07 tscislo

Looks like this may have been fixed with v19.0.0-rc.5. Have not tried though. https://github.com/angular/angularfire/releases/tag/19.0.0-rc.5

Mixed reports above about this occurring in later versions.

madmacc avatar Oct 08 '25 19:10 madmacc