angularfire icon indicating copy to clipboard operation
angularfire copied to clipboard

@angular/[email protected] throws Circular dependency in DI detected for Analytics. when using Analytics feature

Open jakehockey10 opened this issue 1 year ago • 10 comments
trafficstars

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

jakehockey10 avatar Dec 07 '23 22:12 jakehockey10

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

NilsMinor avatar Dec 19 '23 20:12 NilsMinor

image

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

Aball985 avatar Dec 23 '23 05:12 Aball985

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

jakehockey10 avatar Dec 23 '23 15:12 jakehockey10

@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

Aball985 avatar Dec 23 '23 18:12 Aball985

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

jakehockey10 avatar Dec 23 '23 21:12 jakehockey10

@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

Aball985 avatar Dec 23 '23 21:12 Aball985

@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;
  }

...

}

jakehockey10 avatar Dec 24 '23 03:12 jakehockey10

@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

Aball985 avatar Dec 24 '23 13:12 Aball985

@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: https://github.com/angular/angularfire/blob/8157744c53e378c379b0bdedd480332b997f741a/src/auth-guard/auth-guard.ts#L20.

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!

jakehockey10 avatar Dec 24 '23 17:12 jakehockey10

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!

Aball985 avatar Dec 24 '23 22:12 Aball985