2

My users can like a list of heroes, so I have this structure in my firebase rules/datas:

"user_flags": {
  "$uid": {
    ".write": "auth.uid == $uid",
    ".read": "auth.uid == $uid",
    "liked": {
      "$heroIdx": {
        ".validate": "newData.isString()" 
      }
    }
  }
}

In my code I want to subscribe to the "liked heroes" ref, so that's what I do:

import { Injectable } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';

import { AngularFireAuth } from 'angularfire2/auth';

@Injectable()
export class UserFlagsService {
  likedHeroes$: Observable<string[]>;

  constructor(
    private afAuth: AngularFireAuth,
    private db: AngularFireDatabase
  ) {
    this.likedHeroes$ = afAuth.authState.flatMap(user => {
      return user && user.uid
        ? this.db.list(`user_flags/${user.uid}/liked`)
                 .map(heroes => heroes.map(hero => <string>hero.$value))
        : Observable.of([])
    });
  }
}

Everything works fine until the user signs out... Even with the check on user and user.uid the query user_flags/MY_ID_HERE/liked seems to be triggered and I get a "permission denied".

I tried to use subscribe and watch for signout to unsubscribe but it didn't work either... The query was still triggered and failed with "permission denied"

How should I handle this ? I want my service to return a reliable observable so I can subscribe to it in my components.

Thanks a lot for your help

KENdi
  • 7,576
  • 2
  • 16
  • 31
Maslow
  • 1,084
  • 9
  • 22

2 Answers2

3

I am assuming that you want to ensure that the data you are rendering in the view disappears on sign out?

If this is the case, I would suggest using the switchMap operator from RXJS and the following pattern:

this.userProvider = this.afAuth.authState;
this.likedHeroes$ = this.userProvider.switchMap((auth) => {
    if(auth){
        return this.af.list('user_flags/' + auth.uid + '/liked');
    }
});

You need to add the following to import the switchMap operator:

import 'rxjs/add/operator/switchMap';

Hit me with some comments if you want me to fill this out some more or if my assumption about what you're trying to achieve is incorrect. I've been trying to figure out the best way to do this stuff as well.

You should also check out this video from the Angular Firebase YouTube, it might help you with some of the issues in your question.

nclarx
  • 847
  • 1
  • 8
  • 18
  • It may also help to note the difference between flatMap and switchMap, see this: https://stackoverflow.com/a/28180793/6288171 – nclarx Aug 03 '17 at 08:13
  • Stil not comfortable with the difference between flatMap and switchMap but this seems to work very well. Thank you – Maslow Aug 03 '17 at 13:29
  • If someone he's having the same problem with angularfire2-offline, the solution is to use the `reset()` method before calling `this.afAuth.auth.signOut();` – Maslow Aug 03 '17 at 13:31
1

I managed to make it work by creating a BehaviorSubject and unsubscribing the event before the "signout" is triggered. Here is my provider:

import { Injectable } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subscription } from 'rxjs/Subscription';

import { UserProvider } from '../../providers/user/user';

@Injectable()
export class UserFlagsProvider {
  likedHeroes$: Observable<string[]>;
  subHeroesLiked: Subscription;

  constructor(
    protected userProvider: UserProvider,
    protected db: AngularFireDatabase
  ) {
    const heroesLikedSubject: BehaviorSubject<string[]> = new BehaviorSubject([]);

    this.likedHeroes$ = heroesLikedSubject.asObservable();

    this.userProvider.user$.subscribe(user => {
      if (user) {
        this.subHeroesLiked = this.db.list(`user_flags/${user.uid}/liked`).subscribe(heroesSlugs => {
          heroesLikedSubject.next(heroesSlugs.map(hero => <string>hero.$key));
        });
      }
    });

    this.userProvider.signingOut$.subscribe(() => {
      this.subHeroesLiked.unsubscribe();
    });
  }
}

And my userProvider

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { AngularFireAuth } from 'angularfire2/auth';

@Injectable()
export class UserProvider {
  user$: Observable<firebase.User>;
  user: firebase.User;
  signingOut$: Subject<any> = new Subject();

  constructor(private afAuth: AngularFireAuth) {
    this.user$ = afAuth.authState;
    this.user$.subscribe(user => this.user = user);
  }

  // [...]

  signout() {
    this.signingOut$.next();

    return this.afAuth.auth.signOut();
  }
}

Hope it helps someone.

FYI, this doesn't work (for now) with angularfire2-offline for an unknown reason.

If someone sees a better way to do it I'm interested

Maslow
  • 1,084
  • 9
  • 22