Angularfire: AngularFireAuthGuards cause "Cannot instantiate cyclic dependency!" error when in nested routes

Created on 29 Mar 2020  路  14Comments  路  Source: angular/angularfire

Version info

{
  "dependencies": {
    "@angular/animations": "9.1.0",
    "@angular/cdk": "^9.2.0",
    "@angular/common": "9.1.0",
    "@angular/compiler": "9.1.0",
    "@angular/core": "9.1.0",
    "@angular/fire": "^6.0.0-rc.1",
    "@angular/forms": "9.1.0",
    "@angular/material": "^9.2.0",
    "@angular/platform-browser": "9.1.0",
    "@angular/platform-browser-dynamic": "9.1.0",
    "@angular/router": "9.1.0",
    "@nrwl/angular": "9.1.4",
    "core-js": "^2.5.4",
    "firebase": "^7.8.0",
    "moment": "^2.24.0",
    "ngx-toastr": "^12.0.0",
    "normalizr": "^3.6.0",
    "rxjs": "~6.5.0",
    "zone.js": "^0.10.2"
  }
}

How to reproduce these conditions

You may clone this repo (https://github.com/jahumes/angular-fire-error) and run/build the Angular app

Steps to set up and reproduce

Steps to reproduce the error:

  1. Download app and install dependencies
  2. Run npm run start
  3. Visit http://localhost:4205/auth/login
  4. Open console and see the error
  5. To see it fixed, open up src/app/auth/auth-routing.module.ts and replace the routes with the following code:
const routes: Routes = [
  {
    path: 'login',
    component: LoginComponent
  },
  {
    path: 'logging-in',
    component: LoggingInComponent
  }
];

Sample data and security rules

<-- include/attach/link to some json sample data (or provide credentials to a sanitized, test Firebase project) -->

Debug output

* Errors in the JavaScript console *

core.js:6185 ERROR Error: Cannot instantiate cyclic dependency! AngularFireAuth
    at throwCyclicDependencyError (core.js:8035)
    at R3Injector.hydrate (core.js:16861)
    at R3Injector.get (core.js:16617)
    at NgModuleRef$1.get (core.js:36024)
    at Object.get (core.js:33773)
    at getOrCreateInjectable (core.js:5805)
    at Module.傻傻directiveInject (core.js:20861)
    at NodeInjectorFactory.LoginComponent_Factory [as factory] (login.component.ts:11)
    at getNodeInjectable (core.js:5950)
    at instantiateRootComponent (core.js:12585)

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

* Screenshots *

Expected behavior

No error in the console.

Actual behavior

The Angular app appears to be working correctly. I am wondering if this is caused by the AuthGuards dynamically loading the firebase/auth module outside of Angular.

Most helpful comment

I also face the exact same issue as @magoarcano

"dependencies": {
    "@angular/animations": "^9.1.4",
    "@angular/cdk": "^9.2.2",
    "@angular/common": "^9.1.4",
    "@angular/compiler": "^9.1.4",
    "@angular/core": "^9.1.4",
    "@angular/fire": "^6.0.0-rc.2",
    "@angular/forms": "^9.1.4",
    "@angular/platform-browser": "^9.1.4",
    "@angular/platform-browser-dynamic": "^9.1.4",
    "@angular/router": "^9.1.4",
    "@angular/service-worker": "^9.1.4",
    "@nebular/eva-icons": "^5.0.0",
    "@nebular/theme": "^5.0.0",
    "date-fns": "^2.12.0",
    "eva-icons": "^1.1.3",
    "firebase": "^7.14.2",
    "ngx-date-fns": "^6.3.0",
    "rxjs": "~6.5.5",
    "tslib": "^1.11.1",
    "zone.js": "~0.10.3"
  }

All 14 comments

I have found a related issue. Please see (https://github.com/angular/angularfire/issues/2367)

I have spent some more time working on this and discovered that if you take the AngularFireAuthGuard that is provided by @angular/fire, you can fix this issue (and the related one) by removing to code that pushes the auth check outside of Angular (see https://github.com/jahumes/angular-fire-error/tree/guard-fix).

The change looks like:

  constructor(
    @Inject(FIREBASE_OPTIONS) options: FirebaseOptions,
    @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string | FirebaseAppConfig | null | undefined,
    zone: NgZone,
    private fireAuth: AngularFireAuth,
    private router: Router
  ) {
    // const auth = of(undefined).pipe(
    //   observeOn(new 傻AngularFireSchedulers(zone).outsideAngular),
    //   switchMap(() => zone.runOutsideAngular(() => import('firebase/auth'))),
    //   map(() => 傻firebaseAppFactory(options, zone, nameOrConfig)),
    //   map(app => app.auth()),
    //   shareReplay({bufferSize: 1, refCount: false}),
    // );

    // this.authState = auth.pipe(
    //   switchMap(auth => new Observable<User | null>(auth.onAuthStateChanged.bind(auth)))
    // );

    this.authState = this.fireAuth.authState;
  }

Can you help me understand the purpose of importing the code from outside of Angular? I understand wanting to make the auth code import dynamic, but why does this need to run outside of Angular?

Below is an example of the code needed to make everything work as before but with lazy loading the firebase/auth module only when the auth guard is used (see https://github.com/jahumes/angular-fire-error/tree/guard-fix-with-lazy-loaded-firebase-auth)

  constructor(
    @Inject(FIREBASE_OPTIONS) options: FirebaseOptions,
    @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string | FirebaseAppConfig | null | undefined,
    zone: NgZone,
    // private fireAuth: AngularFireAuth,
    private router: Router
  ) {
    const auth = of(undefined).pipe(
      // observeOn(new 傻AngularFireSchedulers(zone).outsideAngular),
      switchMap(() => import('firebase/auth')),
      map(() => 傻firebaseAppFactory(options, zone, nameOrConfig)),
      map(app => app.auth()),
      shareReplay({bufferSize: 1, refCount: false}),
    );

    this.authState = auth.pipe(
      switchMap(auth => new Observable<User | null>(auth.onAuthStateChanged.bind(auth)))
    );

    // this.authState = this.fireAuth.authState;
  }

I have reviewed this again, and it appears that the full fix for this would be (see ):

  constructor(
    @Inject(FIREBASE_OPTIONS) options: FirebaseOptions,
    @Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string | FirebaseAppConfig | null | undefined,
    zone: NgZone,
    private router: Router
  ) {
    const auth = of(undefined).pipe(
      observeOn(new 傻AngularFireSchedulers(zone).outsideAngular),
      switchMap(() => zone.runOutsideAngular(() => import('firebase/auth'))),
      observeOn(new 傻AngularFireSchedulers(zone).insideAngular),
      map(() => 傻firebaseAppFactory(options, zone, nameOrConfig)),
      map(app => app.auth()),
      shareReplay({bufferSize: 1, refCount: false}),
    );

    this.authState = auth.pipe(
      switchMap(auth => new Observable<User | null>(auth.onAuthStateChanged.bind(auth)))
    );
  }

We need to do it outside of Angular because loading firebase/auth has side effects. It loads @firebase/auth and then instantiates to auth library, which in turn creates timers and a bunch of other things we don鈥檛 want to be inside of Angular鈥檚 zone. I鈥檒l check on the PR but yeah my suspicion is that we鈥檙e coming back into Angular鈥檚 zone at the wrong time; if we need to come back into the Angular zone at all.

Thanks for the response. I think you are totally right. I believe that the issue comes when the firebaseAppFactory is called outside of Angular. On lazy loaded routes that are loaded on a full page refresh, I think the app is being instantiated before Angular is finished initializing when done outside of Zone.

This has been fixed in RC2.

I think it's not solved. I still have this error when using AuthGuard and lazy loaded modules

Error: Cannot instantiate cyclic dependency! AngularFireAuth

"dependencies": {
    "@angular-material-extensions/password-strength": "^6.0.0",
    "@angular/animations": "~9.1.3",
    "@angular/cdk": "~9.1.2",
    "@angular/common": "~9.1.3",
    "@angular/compiler": "~9.1.3",
    "@angular/core": "~9.1.3",
    "@angular/fire": "^6.0.0",
    "@angular/flex-layout": "^9.0.0-beta.29",
    "@angular/forms": "~9.1.3",
    "@angular/material": "~9.1.2",
    "@angular/platform-browser": "~9.1.3",
    "@angular/platform-browser-dynamic": "~9.1.3",
    "@angular/router": "~9.1.3",
    "@ng-select/ng-select": "^4.0.0",
    "@ngx-formly/core": "^5.5.15",
    "@ngx-formly/material": "^5.5.15",
    "@ngx-translate/core": "^12.1.2",
    "@ngx-translate/http-loader": "~4.0.0",
    "firebase": "^7.8.1",
    "ngx-auth-firebaseui": "^4.2.1",
    "rxjs": "~6.5.4",
    "tslib": "^1.10.0",
    "zone.js": "~0.10.2"
  },

It happens when I reload a page or access to a page by the url. Not when I navigate to that page with the router.

I also face the exact same issue as @magoarcano

"dependencies": {
    "@angular/animations": "^9.1.4",
    "@angular/cdk": "^9.2.2",
    "@angular/common": "^9.1.4",
    "@angular/compiler": "^9.1.4",
    "@angular/core": "^9.1.4",
    "@angular/fire": "^6.0.0-rc.2",
    "@angular/forms": "^9.1.4",
    "@angular/platform-browser": "^9.1.4",
    "@angular/platform-browser-dynamic": "^9.1.4",
    "@angular/router": "^9.1.4",
    "@angular/service-worker": "^9.1.4",
    "@nebular/eva-icons": "^5.0.0",
    "@nebular/theme": "^5.0.0",
    "date-fns": "^2.12.0",
    "eva-icons": "^1.1.3",
    "firebase": "^7.14.2",
    "ngx-date-fns": "^6.3.0",
    "rxjs": "~6.5.5",
    "tslib": "^1.11.1",
    "zone.js": "~0.10.3"
  }

Same issue. :(

Solved the issue by reordering the imports of modules.
In my case the error occurred because imports were as following: AngularFireAuthModule was after AngularFirstoreModule.
All you have to do is import AngularFireAuthModule before AngularFirestoreModule.

import { AngularFireAuthModule } from '@angular/fire/auth';
import { AngularFirestoreModule } from '@angular/fire/firestore';

@uloga Thank you very much! it was driving me crazy for more than one month.

But now, for the same circunstances I have this other error:
instrument.js:110 Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?
I think it's related to this one somehow. It happens when I refresh the page, and then router.navigate to other page in the component.

Try creating your own redirect service and using it instead of router.navigate.

Service:

import { Injectable, NgZone } from '@angular/core';
import { Router } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class RedirectService {

  constructor(
    private zone: NgZone,
    private router: Router
  ) { }

  navigate(path: any[]){
    return this.zone.run(() => {
      this.router.navigate(path);
    });

  }
}

Use:

    // import it in your file and constructor

   constructor(
        private redirect: RedirectService
   ){}

    login(){
       // login logic
      this.redirect.navigate(['some_url_path']);
    }

Thanks again! Since it's a problem caused by the Auth guards I just solved using the workaround provided here:
https://github.com/angular/angularfire/issues/2444#issue-611975138

Thanks for sharing, good to know in case I run in the same issue.

Was this page helpful?
0 / 5 - 0 ratings