Angularfire: Infinite loop when doing a .map of authState

Created on 7 May 2017  路  3Comments  路  Source: angular/angularfire

Change in the authState stream behavior in 4.0.0-rc0 vs previous versions when doing a .map of that authState stream. Sorry I don't have Plunker ready for this but the repo code using the Angular CLI below should be easy to follow.

Version info

Angular:
"@angular/core": "^4.0.0",

Firebase:
"firebase": "3.9.0",

AngularFire:
"angularfire2": "4.0.0-rc.0",

Other (e.g. Ionic/Cordova, Node, browser, operating system):
Mac 10.12.4, Node v7.5.0, Chrome 57.0.2987.133

How to reproduce these conditions

I was doing a .map of the authState stream to make a boolean stream for signed in status. Something I might use for an auth.gaurd.ts

Failing code demonstrating the problem
app.component.ts

import { Component } from '@angular/core';
import { AngularFireAuth } from "angularfire2/auth";
import * as firebase from 'firebase/auth';
import { Observable } from "rxjs/Observable";
import 'rxjs/add/operator/map';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
    constructor(private afAuth: AngularFireAuth) {
    afAuth.authState.subscribe((user: firebase.User) => {
      if (user && user.uid) {
        console.log("User is signed in. uid: ", user.uid);
      } else {
        console.log("User is not signed in :(");
      }
    });
  }

  get isSignedInStream(): Observable<boolean> {
    return this.afAuth.authState.map<firebase.User, boolean>((user: firebase.User) => {
      console.log("Is signed in stream", user);
      return user != null;
    });
  }
}

app.component.html

Testing for an infinite auth loop
<pre>{{isSignedInStream | async | json}}</pre>
Comment out the line above as your first test to make the .map stream not have a subscriber and therefore never run.  Uncomment the line above activate the isSignedInStream and cause an infinite loop.

Steps to set up and reproduce
ng new AuthInfiniteLoop
cd AuthInfiniteLoop/
npm install angularfire2 firebase --save
(follow the setup steps within environments and app.module.ts. app.module.ts shown below as a reference, environments not shown but it's what you'd guess)
ng serve
Follow the advice in the html file.

app.module.ts
(just to see if I messed anything up)

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';

import { AngularFireModule } from 'angularfire2';
import { environment } from '../environments/environment';
import { AngularFireAuthModule } from 'angularfire2/auth';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    AngularFireModule.initializeApp(environment.firebaseConfig), // imports firebase/app needed for everything
    AngularFireAuthModule, // imports firebase/auth, only needed for auth features
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Debug output (if signed in)

User is signed in. uid: fisherds
U {ba: Array(0), j: "AIzaSyBUZoLZPumsnN_AJLRPAqOswhPT80nmTuo", D: "ME435 Web Remote", v: "fisherds-me435.firebaseapp.com", g: S鈥
auth.service.ts:25 Is signed in stream U {ba: Array(0), j: "AIzaSyBUZoLZPumsnN_AJLRPAqOswhPT80nmTuo", D: "ME435 Web Remote", v: "fisherds-me435.firebaseapp.com", g: S鈥
auth.service.ts:25 Is signed in stream U {ba: Array(0), j: "AIzaSyBUZoLZPumsnN_AJLRPAqOswhPT80nmTuo", D: "ME435 Web Remote", v: "fisherds-me435.firebaseapp.com", g: S鈥
auth.service.ts:25 Is signed in stream U {ba: Array(0), j: "AIzaSyBUZoLZPumsnN_AJLRPAqOswhPT80nmTuo", D: "ME435 Web Remote", v: "fisherds-me435.firebaseapp.com", g: S鈥
... (repeats forever causing chrome tab to go non responsive)

Debug output (if not signed in)

User is not signed in :(
Is signed in stream null
Is signed in stream null
Is signed in stream null
... (repeats forever causing chrome tab to go non responsive)

Expected behavior

Expected one call to my .map stream. As has worked in AngularFire2 pre 4.0.0-rc0 for many months. I have used this approach with my auth.gaurd.ts many times in the past

Actual behavior

Infinite loop where my .map is being called repeatedly forever.

Most helpful comment

@fisherds You are creating an observable inside of a getter. Change detection will call this getter over and over again which will lead to your infinite loop. Can you try to remove the getter and create the isSignedIn observable in the constructor or ngOnInit?

constructor(afAuth: AngularFireAuth) {
  this.isSignedIn = afAuth.authState.map<firebase.User, boolean>((user: firebase.User) => {
      console.log("Is signed in stream", user);
      return user != null;
    });
}

All 3 comments

@fisherds You are creating an observable inside of a getter. Change detection will call this getter over and over again which will lead to your infinite loop. Can you try to remove the getter and create the isSignedIn observable in the constructor or ngOnInit?

constructor(afAuth: AngularFireAuth) {
  this.isSignedIn = afAuth.authState.map<firebase.User, boolean>((user: firebase.User) => {
      console.log("Is signed in stream", user);
      return user != null;
    });
}

Thanks Dave, yeah that totally fixed my issue. In short I've been doing it wrong for awhile, but I was getting away with it before. Thanks for your help!

Thank you!!! This was puzzling me...

Was this page helpful?
0 / 5 - 0 ratings