Can't check if profile exist in firebase with Angular [Ionic] - javascript

Please, I need a help, I'm trying to check if the user profile exists in Firebase database with Angular (Ionic). so if it exists then go to TabsPage, if not go to EditProfiePage.
But it didn't work. it's returning null! and always go to EditProfilePage, Please check codes below:
this.data.getProfile(<User> event.result).subscribe(profile =>{
console.log(profile);
if (profile.hasOwnProperty('$value') && !profile['$value'] )
{
this.navCtrl.setRoot("TabsPage")
}
else {
this.navCtrl.setRoot("EditProfilePage");
}
Data Provider:
public getProfile(user: User){
this.profileObject = this.database.object(`profiles/${user.uid}`);
return this.profileObject.snapshotChanges().pipe(first());
}
See the picture:
Picture: The result is null

Thanks for all answers.
I solved the problem by the following way:
1- I imported Subscription then I declared 'authenticatedUser$' var of type Subscription, then I declared 'authenticatedUser' var of type User (firebase User). - "login.ts"
2- In Constructor I will get current Authenticated User inside 'authenticatedUser$' var, then I will subscribe it into 'authenticatedUser' var. - "AuthProvider.ts"
3- last thing, I will send authenticatedUser var as a parameter to my DataProvider, and also I will subscribe returned value. - "DataProvider.ts"
AuthProvider:
import { LoginResponse } from '../../models/login-response';
import { Injectable } from '#angular/core';
import { AngularFireAuth } from 'angularfire2/auth';
import { Account } from '../../models/account';
....................
constructor(public auth:AngularFireAuth) {
console.log('Hello AuthProvider Provider');
}
public getAuthenticatedUser(){
return this.auth.authState;
}
DataProvider:
import { Injectable } from '#angular/core';
import { AngularFireDatabase, AngularFireObject, AngularFireList } from 'angularfire2/database';
import { User } from 'firebase/app';
import { Profile } from '../../models/profile';
................
profileObject: AngularFireObject<any>;
constructor(public database: AngularFireDatabase) {
}
public getProfile(user: User){
this.profileObject = this.database.object(`profiles/${user.uid}`);
return this.profileObject.valueChanges();
}
login.ts
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams, ToastController } from 'ionic-angular';
import { LoginResponse } from '../../models/login-response';
import { DataProvider } from '../../providers/data/data';
import { User } from 'firebase/app';
import { Subscription } from 'rxjs/Subscription';
import { AuthProvider } from '../../providers/auth/auth';
................
user = {} as User;
private authenticatedUser$: Subscription;
private authenticatedUser: User;
constructor(public navCtrl: NavController,
public toast:ToastController, public navParams: NavParams, public data: DataProvider,
private myAuthProvider:AuthProvider) {
this.authenticatedUser$ = this.myAuthProvider.getAuthenticatedUser()
.subscribe((user: User)=>{
this.authenticatedUser = user
})
this.data.getProfile(this.authenticatedUser).subscribe(profile =>{
console.log(profile);
if (profile) {
this.navCtrl.setRoot("TabsPage")
} else {
this.navCtrl.setRoot("EditProfilePage");
}
});
}

In your data provider
Instead of
return this.profileObject.snapshotChanges().pipe(first());
You can try
return this.profileObject.valueChanges();

There's no need to check if object has property. A simple if statement is enough.
public getProfile(user: User){
this.profileObject = this.database.object(`profiles/${user.uid}`);
return this.profileObject.valueChanges();
}
The if statement checks if the profile exists or not (If it doesn't exist the result is undefined or null)
this.data.getProfile(user).subscribe(profile =>{
if (profile) {
this.navCtrl.setRoot("TabsPage")
} else {
this.navCtrl.setRoot("EditProfilePage");
}
});

Related

How to set Firebase rules for anonymous users to only read and write their own data

I would like anonymous users to be able to only read and write their own data. I have the below as my security rules, but am getting a cannot read error in the simulator and the app.
I'm not sure that I'm going about it the right way. My main objective is to nest new assessments of the same user under their uid's and make it so they can only read, write and update their own assessments.
{
"rules": {
"users": {
"$uid": {
".write": "$uid === auth.uid";
".read": "$uid === auth.uid";
}
}
}
}
This is what my branch currently looks like
This is what I think it should look like to accomplish what I need.
Ideal Database structure
auth.gaurd.ts
import { Injectable } from '#angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '#angular/router';
import { AuthService } from "../../shared/service/auth.service";
import { Observable } from 'rxjs';
import * as firebase from 'firebase';
#Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
uid: string;
constructor(
public authService: AuthService,
public router: Router
){ }
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
this.authStateListener();
return true;
}
authStateListener() {
// [START auth_state_listener]
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
this.uid = user.uid;
console.log("user"+user.isAnonymous)
console.log("uid"+this.uid)
} else {
// User is signed out
return firebase.auth().signOut().then(() => {
localStorage.removeItem('user');
this.router.navigate(['sign-in']);
})
}
});
}
}
auth.service.ts
import { Injectable, NgZone, ViewChild, ElementRef, Component } from '#angular/core';
import { User } from "../service/user";
import { auth } from 'firebase/app';
import { AngularFireAuth } from "#angular/fire/auth";
import { AngularFirestore, AngularFirestoreDocument } from '#angular/fire/firestore';
import { ActivatedRoute, Router } from "#angular/router";
import * as firebase from 'firebase';
import "firebase/auth";
#Injectable({
providedIn: 'root'
})
export class AuthService {
userData: any; // Save logged in user data
#ViewChild('btnLogin') btnLogin: HTMLButtonElement;
constructor(
public afs: AngularFirestore, // Inject Firestore service
public afAuth: AngularFireAuth, // Inject Firebase auth service
public router: Router,
private actRoute: ActivatedRoute,
public ngZone: NgZone // NgZone service to remove outside scope warning
) {
}
anonymousSignIn(){
firebase.auth().signInAnonymously()
.then(()=>{
this.router.navigate(['assessment']);
console.log("clicking")
}).catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
console.log("error here")
});
}
**This is the code to push, read, update and delete branches in Firebase. The ReadAssessment list should display all data that the anonymous user owns in order for them to read it. ** fire-assessment.service.ts
import { AuthGuard } from './../shared/guard/auth.guard';
import { Injectable } from '#angular/core';
import {AngularFireDatabase, AngularFireList, AngularFireObject} from '#angular/fire/database';
import * as firebase from 'firebase';
import {Business} from '../models/business';
import { ActivatedRoute, Router } from '#angular/router';
import { map } from 'rxjs/internal/operators/map';
import { isNgTemplate } from '#angular/compiler';
#Injectable({
providedIn: 'root'
})
export class FireAssessmentService {
assessmentsRef: AngularFireList<any>; // Reference to Assessment data list, its an Observable
assessmentRef: AngularFireObject<any>; // Reference to assessment object
public database = firebase.database();
public UserAssessmentInput;
public ref;
public actRoute: ActivatedRoute;
public router: Router;
public auth: AuthGuard;
constructor(private db: AngularFireDatabase) {
}
CreateAssessment(business: Business ){
const key = this.database.ref('/users').push().key;
this.database.ref('/users').child(key).set(
///this.assessmentsRef.ref('/users').push(
{
busiName: business.busiName
});
}
ReadAssessment(id: string){
this.assessmentRef = this.db.object('users/' + id);
return this.assessmentRef;
}
ReadAssessmentsList(){
this.assessmentsRef = this.db.list('users/');
return this.assessmentsRef;
}
UpdateAssessments (business: Business){
this.assessmentRef.update({
busiName: business.busiName
});
}
DeleteAssessment(){
this.assessmentRef = this.db.object('users/');
this.assessmentRef.remove();
}
business.ts
export interface Business {
$key: string;
busiName: string;
}
Right now you're creating data with this:
const key = this.database.ref('/users').push().key;
this.database.ref('/users').child(key).set({
busiName: business.busiName
});
When you call push() Firebase generates a new unique location, which is the key starting with -M... in your JSON.
That value is not the UID of the current user, so these rules then don't allow the user to read or write it:
"users": {
"$uid": {
".write": "$uid === auth.uid";
".read": "$uid === auth.uid";
}
}
Instead you should write the data under a node using the user's UID as the key. That'd look something like:
const key = this.database.ref('/users').push().key;
if (firebase.auth().currentUser) {
const key = firebase.auth().currentUser.uid;
this.database.ref('/users').child(key).set({
busiName: business.busiName
});
}
else {
console.error("No current user while trying to write business name");
}

Angular method returns undefined

As a beginner, I facing a problem with Angular and Observables. I have API for getting information about one specific restaurant in the database, but I have to get it with a POST request. I successfully get restaurantID from auth.service and another API when the restaurant is logged in, But when I tried to log restaurant in console, I get undefined. Uniformly I don't have permission to show API here. The code:
restaurant.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Restaurant } from '../models/Restaurant';
import { LoggedRestaurant } from '../models/LoggedRestaurant';
#Injectable({
providedIn: 'root'
})
export class RestaurantService {
private restaurantUrl = 'xxxxxxxxxxxx';
public restaurant: Restaurant;
public loggedRestaurant: LoggedRestaurant
public restaurantID;
constructor(private http: HttpClient) { }
public getRestaurant(): Observable<LoggedRestaurant> {
return this.http.post<LoggedRestaurant>(this.restaurantUrl, this.restaurantID);
}
}
informacije.component.ts
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../services/auth.service';
import { RestaurantService } from '../services/restaurant.service';
import { Restaurant } from '../models/Restaurant';
import { LoggedRestaurant } from '../models/LoggedRestaurant';
import { Observable } from 'rxjs';
#Component({
selector: 'app-informacije',
templateUrl: './informacije.component.html',
styleUrls: ['./informacije.component.scss']
})
export class InformacijeComponent implements OnInit {
restaurant: Restaurant;
loggedRestaurant: LoggedRestaurant;
restaurantID;
constructor(private restaurantService: RestaurantService, private authService: AuthService ) { }
getRestaurant() {
return this.restaurantService.getRestaurant()
}
ngOnInit() {
this.restaurant = this.authService.currRestaurant[0];
console.log(this.restaurant)
console.log(this.loggedRestaurant)
this.restaurantID = this.restaurant.id;
console.log(this.restaurantID)
this.restaurantService.restaurantID =this.restaurantID;
}
}
httpClient.post() returns an observable (RXJS). So you need to subscribe to that. Otherwise, you may use the async pipe.
in your html, you can try this,
<span>{{getRestaurant() | aync}}</span>
OR,
you can declare a variable in your ts like data, and,
this.restaurantService.getRestaurant().subscribe(payload => {
this.data = payload;
})
and in your html, you can add,
<span *ngIf="data">{{data}}</span>
You need to subscribe to your API call.
In informacije.component.ts
getRestaurant() {
return this.restaurantService.getRestaurant()
.subscribe(data => this.restaurant = data);
}
This will asign the value returned by your service to your restaurant field in an asynchronous fashion.
In ngOnInit() call getRestaurant as follows
async ngOnInit() {
let restaurant = await this.getRestaurant().toPromise();
...
}

AngularFire2 return 'undefined' [object Object] with Angular 6

I try to get details from the firebase database but keep getting undefined
here is my code for getting the Object from the data base:
import { AppUser } from './models/app-user';
import { Injectable } from '#angular/core';
import { AngularFireDatabase, AngularFireObject } from 'angularfire2/database';
import * as firebase from 'firebase';
#Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private db: AngularFireDatabase ) { }
get(uid: string): AngularFireObject<AppUser> {
console.log(this.db.object('/users/' + uid));
return this.db.object('/users/' + uid);
}
}
the console log from this get method is: [object Object]
I can't find how to get the username or other information of this user.
Here is my AppUser:
export interface AppUser {
email: string;
isAdmin: boolean;
name: string;
}
I found some answers, but it is related to older version of Angular, and is didn't help my issue.
I also saw some answer related to async pipe, but this is in the HTML, and I need the data to be available in a service.ts file.
I need to get the result in my component (not in the html).
I tried to extract the data of the user like that:
get appUser$(): Observable<AppUser> {
return this.user$
.pipe<AppUser>(map(user => {
if ( user ) {
this.userService.get(user.uid);
}
}));
}
but again the log say I got [object Object]...
In my final method that need to use this information:
canActivate() {
return this.auth.appUser$
.pipe(map(user => {
console.log(user);
return user.isAdmin;
}));
}
The console log give undefined
Use JSON.stringify
Console.log(JSON.stringify(this.db.object('/users/' + uid)));
You are returning a AngularFireDatabase.
You want to subscribe to AngularFireObject. so, you have to call :
get(uid: string): AngularFireObject<any> { // <----
console.log(this.db.object('/users/' + uid));
return this.db.object('/users/' + uid).valueChanges(); // <---
}
and than, you can subscribe and reveal the object in your component.
--Updated--
The current object type is AngularFireObject for single object and AngularFireList for a list of objects.
You still have to call .valueChanges() to get the observable.
I've managed to solve it with help from you, and other blogs. Thanks a lot!
Here is the full solution:
The user service.ts file:
import { AppUser } from './models/app-user';
import { Injectable } from '#angular/core';
import { AngularFireDatabase, AngularFireObject } from 'angularfire2/database';
import * as firebase from 'firebase';
#Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private db: AngularFireDatabase ) { }
get(uid: string): AngularFireObject<AppUser> {
return this.db.object('/users/' + uid);
}
}
Here is the Authentication service.ts file:
import { AppUser } from './models/app-user';
import { pipe } from 'rxjs';
import { CanActivate } from '#angular/router';
import { Injectable } from '#angular/core';
import { UserService } from './user.service';
import { map } from 'rxjs/operators';
import * as firebase from 'firebase';
#Injectable({
providedIn: 'root'
})
export class AdminAuthGuard implements CanActivate {
fireBuser: firebase.User;
constructor(private userService: UserService) { }
canActivate() {
this.fireBuser = firebase.auth().currentUser;
return this.userService.get(this.fireBuser.uid)
.valueChanges()
.pipe(map((appUser: AppUser) => {
console.log(appUser.isAdmin);
return appUser.isAdmin;
}));
}
}
console.log(appUser.isAdmin) - give the correct property saved in the database.

How to access private method in spec file (Karma - Angular) [duplicate]

I have been going round in circles trying to unit test a Service (AuthService) that depends upon AngularFireAuth.
I am trying to find a way to mock, or highjack the Observable AngularFireAuth.authState instead of the Service actually talking to Firebase.
Here is my test spec:
import { inject, TestBed } from '#angular/core/testing';
import { AngularFireModule } from 'angularfire2';
import { AngularFireAuth, AngularFireAuthModule } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import 'rxjs/add/observable/of';
// import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Rx';
import { AuthService } from './auth.service';
import { environment } from '../../environments/environment';
const authState: firebase.User = null;
const mockAngularFireAuth: any = { authState: Observable.of(authState) };
describe('AuthService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [AngularFireModule.initializeApp(environment.firebaseAppConfig)],
providers: [
{ provider: AngularFireAuth, useValue: mockAngularFireAuth },
AuthService
]
});
});
it('should be defined', inject([ AuthService ], (service: AuthService) => {
expect(service).toBeDefined();
}));
it('.authState should be null', inject([ AuthService ], (service: AuthService) => {
expect(service.authState).toBe(null);
}));
});
And here is my (simplified) Service:
import { Injectable } from '#angular/core';
import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import { Observable } from 'rxjs/Rx';
#Injectable()
export class AuthService {
private authState: firebase.User;
constructor(private afAuth: AngularFireAuth) { this.init(); }
private init(): void {
this.afAuth.authState.subscribe((authState) => {
if (authState === null) {
this.afAuth.auth.signInAnonymously()
.then((authState) => {
this.authState = authState;
})
.catch((error) => {
throw new Error(error.message);
});
} else {
this.authState = authState;
}
}, (error) => {
throw new Error(error.message);
});
}
public get currentUser(): firebase.User {
return this.authState ? this.authState : undefined;
}
public get currentUserObservable(): Observable<firebase.User> {
return this.afAuth.authState;
}
public get currentUid(): string {
return this.authState ? this.authState.uid : undefined;
}
public get isAnonymous(): boolean {
return this.authState ? this.authState.isAnonymous : false;
}
public get isAuthenticated(): boolean {
return !!this.authState;
}
public logout(): void {
this.afAuth.auth.signOut();
}
}
I get the error Property 'authState' is private and only accessible within class 'AuthService'.
Of course it is, but I don't want to actually access it — I want to mock or highjack it so I can control it's value from within my test spec. I believe I am way off-course with my code here.
Please note I am using version ^4 of AngularFire2 and there were breaking changes introduced; documented here: https://github.com/angular/angularfire2/blob/master/docs/version-4-upgrade.md
Encapsulated members can be reflected.
The hard way:
expect(Reflect.get(service, 'authState')).toBe(null);
The easy way:
expect(service['authState']).toBe(null);
expect((service as any).authState).toBe(null);

Connect the authentication service with the AuthGuard (simple issue)

I guess it's quite simple issue, but unfortunately I don't really know how to deal with it.
I'm trying to connect my UserAuthenticationService service with the ActivationGuard.
UserAuthenticationService.ts:
import {Injectable} from '#angular/core';
import {Http} from '#angular/http';
#Injectable()
export class UserAuthenticationService {
isUserAuthenticated: boolean = false;
username: string;
constructor(private http: Http) {
}
authentication() {
this.http.get(`http://localhost/api/auth/isLogged/${this.username}`)
.subscribe(res => { //^^returns true or false, depending if the user is logged or not
this.isUserAuthenticated = res.json();
},
err => {
console.error('An error occured.' + err);
});
}
}
ActivationGuard.ts
import {Injectable} from '#angular/core';
import {Router, RouterStateSnapshot, ActivatedRouteSnapshot} from '#angular/router';
import {Observable} from 'rxjs/Observable';
import {UserAuthenticationService} from './UserAuthenticationService';
interface CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>|Promise<boolean>|boolean
}
#Injectable()
export class WorksheetAccessGuard implements CanActivate {
constructor(private router: Router, private userService: UserAuthenticationService) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (this.userService) {
this.router.navigate(['/']);
return false;
}
return true;
}
}
Note
It works great, if I just use localStorage to store the information if the user is logged or not:
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (!localStorage.getItem('currentUser')) {
this.router.navigate(['/']);
return false;
}
return true;
}
But how can I connect the service with the guard? Looking forward for any kind of help. Thank you in advance.
If you need any more information, please let me know and I will edit my post.
Call authentication() method of UserAuthenticationService either in constructor or On ngOnit then it sets the isUserAuthenticated variable and use that in the ActivationGuard.ts
UserAuthenticationService.ts:
import {Injectable} from '#angular/core';
import {Http} from '#angular/http';
#Injectable()
export class UserAuthenticationService {
isUserAuthenticated: boolean = false;
username: string;
constructor(private http: Http) {
this.authentication();
}
authentication() {
this.http.get(`http://localhost/api/auth/isLogged/${this.username}`)
.subscribe(res => { //^^returns true or false, depending if the user is logged or not
this.isUserAuthenticated = res.json();
},
err => {
console.error('An error occured.' + err);
});
}
}
ActivationGuard.ts
#Injectable()
export class WorksheetAccessGuard implements CanActivate {
constructor(private router: Router, private userService: UserAuthenticationService) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (this.userService.isUserAuthenticated) {
this.router.navigate(['/']);
return false;
}
return true;
}
}
This is not the right approach for doing it. Every time you call the service , it initialize a new instance and hence you get a false.
You should create a singleton service instance ( via the main module in your app) - where it will contain your app state ( in memory / localstorage)
Then , when you'll call UserAuthenticationService - you won't update its owbn parameter but the main's one ( the singleton).
I suggest you to use a BehaviourSubject ( read about it , it's like a Subject but it also yields its last value without waiting to emit a value manually).
From that point your app can see from anywhere ig the user is logged in or not.

Categories

Resources