Angularfire: Just get the value once with no subscription

Created on 20 Aug 2016  Â·  31Comments  Â·  Source: angular/angularfire

Hi

I want just a snapshot of a node and not subscribe to updates. What is the best way to do this? I tried

var team = this.af.database.object('/teams/' , { preserveSnapshot: true }).first();
team.subscribe(snapshot => {
     // code here....
}

But i get a compile error on the call to first{}

store.ts (187, 92): Property 'first' does not exist on type 'FirebaseObjectObservable<any>'.

What is the best way to no receive updates.

Thanks
Brad

Most helpful comment

Use .take(1), which will kill the subscription once the first value has emitted.

var team = this.af.database.object('/teams/' , { preserveSnapshot: true }).take(1);

All 31 comments

Similar question: #380

Use .take(1), which will kill the subscription once the first value has emitted.

var team = this.af.database.object('/teams/' , { preserveSnapshot: true }).take(1);

Thanks.

do you have any idea about this error: Property 'take' does not exist on type 'FirebaseListObservable

same here, Property 'take' does not exist on type 'FirebaseListObservable'

i figured it out. you should do this: import 'rxjs/add/operator/take'

and maybe also this: import {Observable} from 'rxjs/Rx';

Thank you @andreigiura , I was able to use the .take property.

Hey guys, is it possible to use take in this case?

this.af.database.object('/users/'+this.uid).take(1).update({
              provider: auth.facebook.providerId,
                name: auth.facebook.displayName,
                email: auth.facebook.email,
          });

Whenever the user is logging in, I run this to save his data in my users node. Unlucky, whenever the user logs out I get this error:

FIREBASE WARNING: Exception was thrown by user callback. Error: permission_denied at /users/userID: Client doesn't have permission to access the desired data.
    at Error (native)
    at G (http://localhost:4200/main.bundle.js:75367:36)
    at Object.G (http://localhost:4200/main.bundle.js:75371:86)
    at yh (http://localhost:4200/main.bundle.js:75356:98)
    at nh.h.wd (http://localhost:4200/main.bundle.js:75349:310)
    at af.wd (http://localhost:4200/main.bundle.js:75252:364)
    at vd.hg (http://localhost:4200/main.bundle.js:75250:280)
    at yd (http://localhost:4200/main.bundle.js:75203:464)
    at WebSocket.La.onmessage (http://localhost:4200/main.bundle.js:75202:245)
    at WebSocket.wrapFn [as _onmessage] (http://localhost:4200/main.bundle.js:94097:29) 

which means Firebase keep listening to my user until the page is redirected. so I need try run it only once. any ideas?

I dont think you can use take on an update. The update will be made only once by default. You can only use take with subscribe.

It seems that it's a permission error. You might have some rule on firebase which allows user to read/write data if it's logged in. Do you try to do any operation after the user logged out?

Thanks for the reply @andreigiura.
In the user panel, I have this code:

ngOnInit() {
        this.authService.loadUser().subscribe(() => {
            this.user = this.authService.userData;
        });
  }

Which refers to my loadUser() method to get the data from firebase (I just tried to place take(1) but I still get this permission error):

loadUser() {
      return this.af.database.object('/users/'+this.uid).take(1).map(user => {
            this.userData = user;
        });
  }

In my logout I simply do this:

logout() {
      this.auth.logout();
      this.userData = null;
    this.isLoggedIn = false;
    this.router.navigate(['']);
  }

I think the error occurs because firebase is keeping the connection to my user even after this.auth.logout(); and until the router is navigating, he keeps trying to refresh the data. That's why I need to perform this action only once.

II have a similar setup, i will just show you how i implemented the same functionality. You code looks strange with the mapping on the load user function and then subscribing on the nginit

I'll be happy to check it out. I've been working for days to understand how to structure the angular 2 app to work good with the Firebase authentication.
I need to simply save the user data into my database and load it once he logins. that's what it does now, but it's does't feels like it is the best way to do this.

this is how my authService.ts looks like:

import { Injectable } from '@angular/core';
import { Http, Response, RequestOptions, Headers } from '@angular/http';
import { Router} from '@angular/router';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';
import { Events} from './Events';

import { AngularFire, AuthProviders, AuthMethods, FIREBASE_PROVIDERS, FirebaseAuthState } from 'angularfire2';

@Injectable()
export class AuthService {
public isLoggedIn;

public usersCollection;
public userData = null;

constructor (public http : Http, private router: Router, private eventEmitter : Events, public af: AngularFire) {
this.isLoggedIn = false;

//subscribe to the auth state
this.af.auth.subscribe(auth => {
  //if the result is not null, setup the public user data which can be accesed through this service
  if(auth != null) {

    this.userData = auth;
    this.isLoggedIn = true;

    //set a route in the users table for this particular user
    this.usersCollection = af.database.object("users/" + this.userData.uid);

    //current date to be saved in the users details, this will be used for the registered at field
    var today = new Date().toLocaleDateString('en-GB', {
        day : 'numeric',
        month : 'short',
        year : 'numeric'
    }).split(' ').join(' ');

    //other data that i need to set in the users table
    var dataToSet = {
      timestamp : Math.floor((new Date()).getTime() / 1000),
      displayName : this.userData.auth.providerData[0].displayName,
      email : this.userData.auth.providerData[0].email,
      photoURL : this.userData.auth.providerData[0].photoURL,
      providerId : this.userData.auth.providerData[0].providerId,
      registeredAt : ""
    };

    //try to get the user from db
    af.database.object("users/" + this.userData.uid).take(1).subscribe((_data) => {
        //if the user does not exist, add the registered at field that we composed else use the entry from the db
          if(!_data.registeredAt) {
            dataToSet.registeredAt = today;
          }else{
            dataToSet.registeredAt = _data.registeredAt;
          }

          //make a hard update to the object (this means that all the data on this firebase route will be replaced and not updated)
          this.usersCollection.set(dataToSet).then((_data) => {
              }).catch((_error) => {
                  console.log(_error)
              })


        })
  }else{

    //set the user details to null
    this.userData = null;
    this.isLoggedIn = false;
  }
});

}

//login with email, password
login(username, password) {
var self = this;
this.af.auth.login({ email : username, password : password },
{ provider: AuthProviders.Password, method: AuthMethods.Password })
.catch(function(err) {
self.eventEmitter.loginErrorsF(err);
})
}

//login with facebook
loginWithFacebook () {
this.af.auth.login({provider: AuthProviders.Facebook,
method: AuthMethods.Popup,
scope: ['email']})
}

//login with google
loginWithGoogle () {
this.af.auth.login({provider: AuthProviders.Google,
method: AuthMethods.Popup,
scope: ['email'] })
}

//signup with email and pwd
signup(username, password) {
var self = this;
this.af.auth.createUser({ email : username, password : password })
.catch(function(err) {
self.eventEmitter.loginErrorsF(err);
})
}

//check login is used by auth guard in order to secure the routes
checkLogin() {
return this.af.auth
.take(1)
.map((authState: FirebaseAuthState) => !!authState)
.do(authenticated => {
if (!authenticated) this.router.navigate(['/login']);
});
}

logout() {
this.af.auth.logout()
}
}

export const AUTH_SERVICE_PROVIDER = [
AuthService
];

This is the authGuard.ts which uses authservice checklogin function in order to secure routes:

import { Injectable } from '@angular/core';
import { CanActivate,
Router,
ActivatedRouteSnapshot,
RouterStateSnapshot } from '@angular/router';
import { AuthService } from './AuthService';

@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}

canActivate(
// Not using but worth knowing about
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
) {
return this.authService.checkLogin();
}
}

export const AUTH_GUARD_PROVIDERS = [
AuthGuard
];

this is the router which uses the authguard for the secured routes (this will not allow access):

import { HomeComponent } from '../components/home/HomeComponent';

import { AuthGuard } from './Auth/AuthGuard';

import { Routes, RouterModule } from '@angular/router';

export const SunmoolAppRoutes = [
{ path: 'home/:category', component: HomeComponent, canActivate : [AuthGuard]},
{ path: 'login', component: LoginComponent},
{ path: '', redirectTo: 'login', pathMatch: 'full' }
]

export const routing = RouterModule.forRoot(SunmoolAppRoutes);

Hey @andreigiura, few questions:

  1. How do you redirect the user to user panel route after the authorization check? I mean, you don't want to show the main app login page whenever someone access your application.
  2. When you log out from your app, don't you get error_handler.js:47 EXCEPTION: permission_denied at /users/userID: Client doesn't have permission to access the desired data. ? I cloned your service and guard, and in my user panel I get this error and the user is not redirection back to login/app page.

Hey, i have set up a simple example on github, the repo is here: https://github.com/andreigiura/simple-angular2-firebase-auth

You will need to add your firebase credentials in: src/app/app.module.ts

This works with the latest angular-cli, so make sure you npm install -g angular-cli

as you will see there, in the login component you will listen for the auth state, if it will change from the null state, you will redirect the user to the homepage

Yeah this works great. I did everything similiar but when I log out I get FIREBASE WARNING: Exception was thrown by user callback. Error: permission_denied at /users/id: Client doesn't have permission to access the desired data. warning in my console. It's also not "kicking"the user back to the login page.

What happens when you click log out in your app?

If you click logout, you are redirected to the login page. I don't get the error, check the repo. The error you get seems to be a firebase rule error. You might have the read/write blocked while the user is not logged in and when you are making the logout, you probably logout successfully and after that you try to do some other operation on the firebase which will throw this error. Just to prove that what i said is right i have made an example with "rules":
{
".read": "auth != null",
".write": "auth != null",

}

in my firebase rules and than i tried to make a read from firebase. I get the exact same error as you do.

main.4cd3da4….bundle.js:29 Error: permission_denied at /items: Client doesn't have permission to access the desired data.
at Error (native)
at G (http://sunmool.umbrellasoftware.ro/main.4cd3da4e9edd4e76637f.bundle.js:50:20875)
at Object.G (http://sunmool.umbrellasoftware.ro/main.4cd3da4e9edd4e76637f.bundle.js:50:23045)
at http://sunmool.umbrellasoftware.ro/main.4cd3da4e9edd4e76637f.bundle.js:50:16137
at nh.h.wd (http://sunmool.umbrellasoftware.ro/main.4cd3da4e9edd4e76637f.bundle.js:52:10092)
at af.wd (http://sunmool.umbrellasoftware.ro/main.4cd3da4e9edd4e76637f.bundle.js:51:15299)
at vd.hg (http://sunmool.umbrellasoftware.ro/main.4cd3da4e9edd4e76637f.bundle.js:49:28617)
at yd (http://sunmool.umbrellasoftware.ro/main.4cd3da4e9edd4e76637f.bundle.js:49:15973)
at bound WrappedWebSocket.La.onmessage (http://sunmool.umbrellasoftware.ro/main.4cd3da4e9edd4e76637f.bundle.js:51:3445)
at bound WrappedWebSocket.t as _onmessage

Further from this i can only pinpoint your problem if i get your source code

@andreigiura Your rules have security risk. Other users can read and write data on other users!
The rules have to be:

"users": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
    },

Change it and you will get the same error as I do.
But no longer! Solved it by changing my logout method:

logout() {
      this.userSub.unsubscribe();
      this.auth.logout();
      this.router.navigate[''];
  }

I had to unsubscribe from the subscription in order to stop receiving updates which caused this error.

I am aware about the security risk for my security rules and it was not a recommandation to turn your security off. I just said that you should review the whole code and make sure that you are not doing any other operations on firebase after you logged out.

Thanks @andreigiura your code worked for me.. https://github.com/angular/angularfire2/issues/456#issuecomment-254464619
here i added a specific user as admin like this,
` //-----

    var isSuper = false;
    var isSuperEmail = "*******@gmail.com";
    if(email == isSuperEmail){
        isSuper = true;
    }`

and in dataToSet,
`//-----

     var dataToSet = {
      timestamp : Math.floor((new Date()).getTime() / 1000),
      displayName : this.userData.auth.providerData[0].displayName,
      email : this.userData.auth.providerData[0].email,
      photoURL : this.userData.auth.providerData[0].photoURL,
      providerId : this.userData.auth.providerData[0].providerId,
      registeredAt : "",
      isSuper : isSuper
    };`

it works fine... but i am not sure writing admin email in component.ts file is safe or not??
is there any other way to do??

@andreigiura
In order to make sure you dont get the client permission on logout, you have to make sure to unsubscribe from every database query subscription you made. That is, for every this.af.database.list.subscribe or this.af.database.object.subscribe, you gotta make sure you implement ngOnDestroy method in that component where you subscribed. Inside ngOnDestroy() you gotta unsubscribe them. Dont try to unsubscribe from this.af.auth upon logout. this.af.auth.subscribe does not create client permission error, only "this.af.database" ones do.

For example, look at the following codes below from one of my angular2 firebase projects:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FirebaseService } from '../../services/firebase.service';

@Component({
selector: 'app-listings',
templateUrl: './listings.component.html',
styleUrls: ['./listings.component.css']
})
export class ListingsComponent implements OnInit, OnDestroy {
listings:any;
private subscription;

constructor(public firebaseService:FirebaseService) { }

ngOnInit() {
this.subscription = this.firebaseService.getListings().subscribe(listings => {
this.listings = listings;
});
}

ngOnDestroy() {
this.subscription.unsubscribe();
}

}

@andreigiura

Code below for origin of getListings() called:

constructor(public af: AngularFire,
public router : Router) {
this.folder = 'listingImages';
this.listings = this.af.database.list('/listings') as FirebaseListObservable .............................
}

getListings () {
return this.listings;
}


Hope that helps

@zeerulz90 do you have a git / plunker repository where this subscription mechanism is used? I would like to have a closer look at it. Thanks )

When to get once a collection like that

var team = this.af.database.object('/teams/' , { preserveSnapshot: true }).take(1);

How to refresh the collection after added an item ?

FirebaseListObservable is updated to angularfireobject, how can i use take???
Plz help, its showing take is not a property

Was this page helpful?
0 / 5 - 0 ratings