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

@angular/fire@17.0.0 throws Circular dependency in DI detected for Analytics. when using Analytics feature #3476

Open
jakehockey10 opened this issue Dec 7, 2023 · 10 comments

Comments

@jakehockey10
Copy link

jakehockey10 commented Dec 7, 2023

Version info

Versions:

$ ng version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/
    

Angular CLI: 17.0.6
Node: 20.1.0
Package Manager: npm 9.7.1
OS: linux x64

Angular: 17.0.6
... animations, cli, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1700.6
@angular-devkit/build-angular   17.0.6
@angular-devkit/core            17.0.6
@angular-devkit/schematics      17.0.6
@angular/cdk                    17.0.2
@angular/fire                   17.0.0
@angular/google-maps            17.0.2
@angular/material               17.0.2
@schematics/angular             17.0.6
ng-packagr                      17.0.0
rxjs                            7.8.1
typescript                      5.2.2
zone.js                         0.14.2

How to reproduce these conditions

Failing test unit, Stackblitz demonstrating the problem

Steps to set up and reproduce

in app.config.ts, provide the analytics feature:

importProvidersFrom([
  provideFirebaseApp(() => initializeApp(environment.firebase)),
  provideAnalytics(() => getAnalytics()),
  provideAuth(() => getAuth()),
  provideFirestore(() => getFirestore()),
  provideFunctions(() => {
    const functions = getFunctions();
    // connectFunctionsEmulator(functions, 'localhost', 5001);
    return functions;
  }),
  providePerformance(() => getPerformance()),
  provideStorage(() => getStorage()),
]),

Sample data and security rules

Debug output

** Errors in the JavaScript console **

debug.operator.ts:31 ERROR FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created - call initializeApp() first (app/no-app).
    at getApp (index.esm2017.js:479:29)
    at getAnalytics (index.esm2017.js:1059:35)
    at angular-fire.mjs:202:48
    at angular-fire.mjs:134:59
    at _ZoneDelegate.invoke (zone.js:368:26)
    at Zone.run (zone.js:129:43)
    at NgZone.runOutsideAngular (core.mjs:14591:28)
    at runOutsideAngular (angular-fire.mjs:134:35)
    at angular-fire.mjs:202:21
    at firebase.provider.ts:22:42
ERROR Error: NG0200: Circular dependency in DI detected for Analytics. Find more at https://angular.io/errors/NG0200
    at throwCyclicDependencyError (core.mjs:292:11)
    at R3Injector.hydrate (core.mjs:6162:13)
    at R3Injector.get (core.mjs:6037:33)
    at angular-fire-analytics.mjs:196:40
    at _ZoneDelegate.invoke (zone.js:368:26)
    at Object.onInvoke (core.mjs:14695:33)
    at _ZoneDelegate.invoke (zone.js:367:52)
    at Zone.run (zone.js:129:43)
    at zone.js:1257:36
    at _ZoneDelegate.invokeTask (zone.js:402:31)

If I comment out where I provide Analytics, then Performance runs into an issue:

main.ts:6 NullInjectorError: R3InjectorError(Standalone[AppComponent])[Performance -> Performance -> Performance]: 
  NullInjectorError: No provider for Performance!
    at NullInjector.get (core.mjs:5605:27)
    at R3Injector.get (core.mjs:6048:33)
    at R3Injector.get (core.mjs:6048:33)
    at R3Injector.get (core.mjs:6048:33)
    at ChainedInjector.get (core.mjs:15400:36)
    at lookupTokenUsingModuleInjector (core.mjs:4116:39)
    at getOrCreateInjectable (core.mjs:4164:12)
    at ɵɵdirectiveInject (core.mjs:11974:19)
    at ɵɵinject (core.mjs:917:60)
    at inject (core.mjs:1001:12)

** Output from firebase.database().enableLogging(true); **

** Screenshots **

Expected behavior

Actual behavior

@NilsMinor
Copy link

I have the same issue with the latest version 17.0.0 for AngularFireAuth. I get a circular dependency issue:

NullInjectorError: R3InjectorError(Standalone[ExampleComponent])[FirebaseAuthService -> FirebaseAuthService -> FirebaseAuthService -> AngularFireAuth -> InjectionToken angularfire2.app.options -> InjectionToken angularfire2.app.options]: 
  NullInjectorError: No provider for InjectionToken angularfire2.app.options!
    at NullInjector.get (core.mjs:5606:27)
    at R3Injector.get (core.mjs:6049:33)
    at R3Injector.get (core.mjs:6049:33)
    at injectInjectorOnly (core.mjs:911:40)
    at Module.ɵɵinject (core.mjs:917:60)
    at Object.AngularFireAuth_Factory [as factory] (angular-fire-compat-auth.mjs:147:96)
    at core.mjs:6169:43
    at runInInjectorProfilerContext (core.mjs:867:9)
    at R3Injector.hydrate (core.mjs:6168:17)
    at R3Injector.get (core.mjs:6038:33)

If it is helpful I am happy to share the code

@Aball985
Copy link

image

Any idea how to fix this issue also using standalone components?

@jakehockey10
Copy link
Author

@Aball985 can you share more of your setup/code? I don't have issues with anything other than analytics and performance

@Aball985
Copy link

@jakehockey10 sure thing here are some screenshots of how I am using angular fire this was my first time setting it up
image
image
image
This is what caused the errors to appear below
image
was trying to add guard protection for un-authed users
image
image
let me know if I am missing something you would also like to see

@jakehockey10
Copy link
Author

@Aball985 I'll take a look in a few hours. Thanks for sharing. I have guards working on my end so I'd like to see if I can help you out!

@Aball985
Copy link

@Aball985 I'll take a look in a few hours. Thanks for sharing. I have guards working on my end so I'd like to see if I can help you out!

thanks! idk if you have your auth working as well but I am seeing this weird console error with the signInWithPopup method as well
image
image

@jakehockey10
Copy link
Author

jakehockey10 commented Dec 24, 2023

@Aball985 I tried attaching the AuthGuard to a route and didn't get any errors in the console unfortunately :( But after taking a look at your screenshots again, I did notice that your firebaseProviders isn't being included in the providers array of your ApplicationConfig, so maybe that's it?

Here is how I've been guarding my routes in my application:

import { canActivate, redirectUnauthorizedTo } from '@angular/fire/auth-guard';

export const profilePageRoute = {
  path: 'profile',
  loadComponent: () => import('./profile-page.component'),
  ...canActivate(() => redirectUnauthorizedTo(['/auth/login'])),
  title: 'Profile',
};

I don't use the signInWithPopup(new GoogleAuthProvider()), but you want to be careful mixing the compat library components (AngularFireAuth, for example). Instead, you want to import the function signInWithPopup from @angular/fire/auth and pass in your Auth instance that you've injected into the NavbarComponent. For example:

import {
  Auth,
  EmailAuthProvider,
  createUserWithEmailAndPassword,
  getIdToken,
  reauthenticateWithCredential,
  sendEmailVerification,
  sendPasswordResetEmail,
  signInWithEmailAndPassword,
  signOut,
  updatePassword,
  updateProfile,
  user,
} from '@angular/fire/auth';

...


@Injectable({
  providedIn: 'root',
})
export class AuthService {
  private readonly _auth = inject(Auth);

...

  async login(email: string, password: string) {
    const credential = await signInWithEmailAndPassword(
      this._auth,
      email,
      password
    );

    return credential;
  }

...

}

@Aball985
Copy link

Aball985 commented Dec 24, 2023

@jakehockey10 thanks for the reply
So I did add like you said and then no errors happened but AuthGuard just didnt do anything
image
then I tried your way I also found out there is a redirectUnauthorizedTo / redirectLoggedInTo
image
I'm not used to this spread syntax for redirection like this it looks kind of odd but it seems to work but for some reason when user is logged out / logged in the page doesn't update till refresh I am guessing I also need to add something into the component itself to listen for auth and redirect if true/false? would this logic also need to be added to every component? I thought that was the Guards job to handle navigation if Auth true/false unless there is better way it seems to only account for on init and not as user is logged in / logged out -> navigate

@jakehockey10
Copy link
Author

jakehockey10 commented Dec 24, 2023

@Aball985 that is weird that AuthGuard by itself didn't work as expected. I'd have to look into it more. But the reason the spread syntax is being used here is that @angular/fire's canActivate function is providing multiple properties for the Route object. Take a look at:

canActivate = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
.

I'm not sure I follow the last issue you mention. Are you saying that, for example, when you are logged out, but then you login, you are not automatically redirected away from the login page? If this is what you are saying, then yes, the auth guards are not going to handle redirecting away from the page for you. Guards only get executed when the user or the developer causes a route change. The route change "asks" the guard if "it's okay to navigate there." But if you want to go away from the login page after successful login, you'll want to handle that with your code that is signing the user in.

For example, my login-page.component uses my AuthService by calling the login method I shared above:

@Component({
  selector: 'app-login-page',
  standalone: true,
  imports: [
    RouterLink,
    ReactiveFormsModule,
    MatFormFieldModule,
    MatInputModule,
    MatCardModule,
    MatButtonModule,
  ],
  templateUrl: './login-page.component.html',
  styleUrls: ['./login-page.component.scss'],
})
export class LoginPageComponent {
  readonly #fb = inject(FormBuilder);
  readonly #router = inject(Router);
  readonly #auth = inject(AuthService);
  readonly #snacks = inject(MatSnackBar);

  readonly loading = signal(false);

  readonly form = this.#fb.nonNullable.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.minLength(8), Validators.required]],
  });

  protected async onSubmit() {
    this.loading.set(true);
    const { email, password } = this.form.getRawValue();

    try {
      const credential = await this.#auth.login(email, password);
      this.#snacks.open(
        `Welcome back, ${credential.user.displayName || credential.user.email}!`
      );
      this.#router.navigate(['/']);  // <--- Navigating away from the login page when sign in is successful
    } catch (error) {
      if (error instanceof FirebaseError) {
        switch (error.code) {
          case 'auth/user-not-found':
          case 'auth/wrong-password':
            this.#snacks.open('Invalid email or password');
            break;
          default:
            this.#snacks.open('An unknown error occurred');
            break;
        }
      }
    }

    this.loading.set(false);
  }
}

Also, if you haven't already, I'd take a look at this documentation here: https://github.com/angular/angularfire/blob/master/docs/auth.md

Hope that helps! Keep me posted!

@Aball985
Copy link

Ah yes I figured it out I had to make my login component handle routerNavigation also after login logout its working as intended now when switching routes as expected thanks for showing me this!

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

3 participants