Ionic 4 & Firebase integration - firebase

So, I'm quite a noob when it comes to ionic 4 firebase integration. Basically I've managed to get the firebase authentication to work, but the problem is I can't seem to save the user's input in the sign up page to firebase database. I need it to work as I would like to link the user to his/her checklist.
This is my sign-up page.ts
import { Component, OnInit } from '#angular/core';
import { Router } from "#angular/router";
import { Platform, AlertController } from '#ionic/angular';
import { LoadingController, ToastController } from '#ionic/angular';
import { AngularFireAuth } from '#angular/fire/auth';
//disable side menu
import { MenuController } from '#ionic/angular';
#Component({
selector: 'app-signup',
templateUrl: './signup.page.html',
styleUrls: ['./signup.page.scss'],
})
export class SignupPage implements OnInit {
email: string = '';
password: string = '';
error: string = '';
username: string = '';
constructor(
private fireauth: AngularFireAuth,
public router: Router,
public menuCtrl: MenuController,
private toastController: ToastController,
private platform: Platform,
public loadingController: LoadingController,
public alertController: AlertController
) { }
async openLoader() {
const loading = await this.loadingController.create({
message: 'Please Wait ...',
duration: 2000
});
await loading.present();
}
async closeLoading() {
return await this.loadingController.dismiss();
}
signup() {
this.fireauth.auth.createUserWithEmailAndPassword(this.email, this.password)
.then(res => {
if (res.user) {
console.log(res.user);
this.updateProfile();
}
})
.catch(err => {
console.log(`login failed ${err}`);
this.error = err.message;
});
}
updateProfile() {
this.fireauth.auth.onAuthStateChanged((user) => {
if (user) {
console.log(user);
user.updateProfile({
displayName: this.username
})
.then(() => {
this.router.navigateByUrl('/login');
})
}
})
}
async presentToast(message, show_button, position, duration) {
const toast = await this.toastController.create({
message: message,
showCloseButton: show_button,
position: position,
duration: duration
});
toast.present();
}
ionViewWillEnter (){
this.menuCtrl.enable(false);
}
ngOnInit(){}
}

Actually, you aren't using the firebase database. The user.updateProfile method stores the displayName property in the Firebase Auth system. That information is saved in the Firebase Auth system.

Related

Vue3 - Pinia + Auth0 - isAuthenticated always false

I'm developing a vue3 app using pinia as state manager and auth0 as authprovider.
In my vue router, I've the following code to manage the authentication:
router.beforeEach(async (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
const authStore = useAuthStore();
const isLogged = authStore.isLogged();
if (!isLogged) await handleNotLogged(to, from, next);
else await handleLogged(to, from, next);
});
async function handleNotLogged(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
const authStore = useAuthStore();
if (to?.query?.code && to?.query?.state) {
next({ name: '/logged/home' });
} else {
await authStore.login();
}
}
async function handleLogged(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {next()}
here is my authStore
import { defineStore } from 'pinia';
import { User } from '#models/user';
import { useStorage } from '#vueuse/core';
import { RouteLocation } from 'vue-router';
import { createAuth0 } from '#auth0/auth0-vue';
const authService = createAuth0({
domain: import.meta.env.VITE_APP_AUTH_URL,
client_id: import.meta.env.VITE_APP_AUTH_CLIENT_ID,
redirect_uri: `${window.location.origin}`,
});
const defaultUserData = {} as User;
const defaultLastRoute = { path: '/' } as RouteLocation;
export const useAuthStore = defineStore('AuthStore', {
state: () => ({
userData: useStorage('userData', defaultUserData, localStorage),
lastRoute: useStorage('lastRoute', defaultLastRoute, localStorage),
authService,
}),
actions: {
isLogged(): boolean {
try {
return this.authService.isAuthenticated;
} catch (error) {
return false;
}
},
async login(): Promise<boolean> {
try {
await this.authService.loginWithRedirect();
return true;
} catch (error) {
console.error(error);
return false;
}
},
async logout(): Promise<boolean> {
try {
await this.authService.logout();
return true;
} catch (error) {
console.error(error);
return false;
}
},
},
});
And also my main.ts
import App from './App.vue';
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import { registerPlugins } from '#plugins';
import { useAuthStore } from '#store/auth';
import router from '#router';
import vuetify from './plugins/vuetify';
async function main() {
const app = createApp(App);
registerPlugins();
const pinia = createPinia();
app.use(pinia);
const authStore = useAuthStore();
const { authService } = authStore;
app.use(authService);
app.use(router);
app.use(vuetify).mount('#app');
}
main();
The problem is that everytime the beforeEach is triggered, the auth0 isAuthenticated returns false. Even when i've just succesfully logged.
I've searched for some answers, and some said that ewhen there is a code and state in query params we should call the auth0.handleRedirectCallback but there's a note in the method saying
Note: The Auth0-Vue SDK handles this for you, unless you set skipRedirectCallback to true. In that case, be sure to explicitly call handleRedirectCallback yourself.
PS: The application in auth0 is configured as Single Page Application
There is already a topic with this question answered, I believe this one can help you:
Auth0 isAuthenticated() is always false

Expected Argument Error for .doc() when called on Firestore Collection

I want to create a new user document in my Cloud Firestore database whenever a new user logs in. Each doc should have a unique id and I want a "uid" property for each user to match the unique auto-generated id for the doc. At first, I just always ran an update on the user, but I figured it could be helpful to separate my create and update logic. As you can see I haven't worked out how to query if a user exists, but I figured I should test the createUser function before continuing.
Anyway, while I was testing my createUser function I ran into a compilation error.
ERROR in src/app/services/auth.service.ts(64,22): error TS2554:
Expected 1 arguments, but got 0.
UPDATE:
When I try to run the function from localhost after compilation I get this error in the console.
Function CollectionReference.doc() requires its first argument to be
of type string, but it was: undefined
Here is my proposed solution:
import { Injectable } from '#angular/core';
import { User } from './../models/user.model';
import { PermissionsService } from './permissions.service';
import { auth } from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import {
AngularFirestore,
AngularFirestoreDocument,
AngularFirestoreCollection,
} from 'angularfire2/firestore';
import { Observable, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';
#Injectable({
providedIn: 'root',
})
export class AuthService {
usersCollection: AngularFirestoreCollection<User> = null;
user: Observable<User>;
constructor(
private afAuth: AngularFireAuth,
private db: AngularFirestore,
private permissionsService: PermissionsService,
) {
this.usersCollection = db.collection('users');
this.user = this.afAuth.authState.pipe(
switchMap((user) => {
if (user) {
return this.db
.doc<User>(`users/${user.uid}`)
.valueChanges();
} else {
return of(null);
}
}),
);
}
loginGoogle() {
const provider = new auth.GoogleAuthProvider();
return this.oAuthLogin(provider);
}
loginFacebook() {
const provider = new auth.FacebookAuthProvider();
return this.oAuthLogin(provider);
}
loginTwitter() {
const provider = new auth.TwitterAuthProvider();
return this.oAuthLogin(provider);
}
oAuthLogin(provider) {
return this.afAuth.auth.signInWithPopup(provider).then((credential) => {
//if(the user exists already)
//this.updateUserData(credential.user);
//else
this.createUser();
});
}
createUser() {
const newUserRef = this.usersCollection.doc<User>(); // Error here
let newUser: User;
this.user.subscribe((userData) => {
newUser = {
uid: newUserRef.id,
email: userData.email,
photoURL: userData.photoURL,
displayName: userData.displayName,
roles: {
member: true,
},
permissions: this.permissionsService.memberPermissions;
};
});
newUserRef
.set(newUser)
.then(() => {
console.log('created user');
})
.catch((err) => {
console.log('Error adding user: ' + err);
});
}
updateUserData(user) {
const userRef: AngularFirestoreDocument<any> = this.db.doc(
`users/${user.uid}`,
);
const userPermissions = this.addPermissions(userRef);
console.log(userPermissions); // This works
const data: User = {
uid: user.uid,
email: user.email,
photoURL: user.photoURL,
displayName: user.displayName,
roles: {
member: true,
}, // I need to make sure this keeps current user roles
permissions: userPermissions,
};
console.log(data); // This works
userRef
.set(data)
.then(() => {
console.log('Success: Data for userDoc overwritten');
})
.catch((err) => {
console.error('Error writing to userDoc: ' + err);
});
}
addPermissions(userRef) {
const tempPermissions = [];
userRef.valueChanges().subscribe((userdata) => {
if (userdata.roles.reader === true) {
tempPermissions.push(this.permissionsService.memberPermissions);
}
if (userdata.roles.author === true) {
tempPermissions.push(this.permissionsService.authorPermissions);
}
if (userdata.roles.admin === true) {
tempPermissions.push(this.permissionsService.adminPermissions);
}
});
return tempPermissions;
}
checkPermissions(permission: string) {
if (!this.user) {
return false;
} else {
this.user.subscribe((data) => {
for (const p of data.permissions) {
if (p === permission) {
return true;
}
}
return false;
});
}
}
logout() {
this.afAuth.auth.signOut();
this.user = null;
}
}
I checked the documentation on the .doc() function and it should work fine with 0 arguments. It should be returning an empty doc reference. However, it keeps throwing the error saying it expects 1 argument. Any idea why this isn't working?

Angular 6, Firebase Storage web Image gallery

I've got a problem with downloading URL during uploading an image file.
I want to use this url to show this images from Firebase storage.
Uplad is working, because i can see this photos on my storage on firebase, but i can't download it and show in my web
Here's a upload.service.ts
import { Injectable } from '#angular/core';
import { AngularFireModule } from 'angularfire2';
import { GalleryImage } from '../models/galleryImage.model';
import { AngularFireDatabase} from 'angularfire2/database';
import { FirebaseListObservable, FirebaseObjectObservable } from 'angularfire2/database-deprecated';
import { Upload } from '../models/upload.model';
import * as firebase from 'firebase';
#Injectable()
export class UploadService {
private basePath = '/uploads';
private uploads: FirebaseListObservable<GalleryImage[]>;
constructor(private ngFire: AngularFireModule, public db: AngularFireDatabase) { }
uploadFile(upload: Upload) {
const storageRef = firebase.storage().ref();
const uploadTask = storageRef.child(`${this.basePath}/${upload.file.name}`)
.put(upload.file);
uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED,
// three observers
// 1.) state_changed observer
(snapshot) => {
// upload in progress
upload.progress = (uploadTask.snapshot.bytesTransferred / uploadTask.snapshot.totalBytes) * 100;
console.log(upload.progress);
},
// 2.) error observer
(error) => {
// upload failed
console.log(error);
},
// 3.) success observer
(): any => {
uploadTask.snapshot.ref.getDownloadURL().then(function(downloadURL) {
upload.url = downloadURL;
console.log('FirstURL', downloadURL);
console.log('SecondURL', upload.url);
this.saveFileData(upload);
});
}
);
}
private saveFileData(upload: Upload) {
this.db.list(`${this.basePath}/`).push(upload);
console.log('File saved at' + upload.url);
}
}
Here's my image.service.ts
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFireDatabase } from 'angularfire2/database';
import { FirebaseApp } from 'angularfire2';
import 'firebase/storage';
import { GalleryImage } from '../models/galleryImage.model';
import * as firebase from 'firebase';
#Injectable()
export class ImageService {
private uid: string;
constructor(private afAuth: AngularFireAuth, private db: AngularFireDatabase) {
this.afAuth.authState.subscribe(auth => {
if (auth !== undefined && auth !== null) {
this.uid = auth.uid;
}
});
}
getImages(): Observable<GalleryImage[]> {
return this.db.list('uploads').valueChanges();
}
getImage(key: string) {
return firebase.database().ref('uploads/' + key).once('value')
.then((snap) => snap.val());
}
}
Here's my upload.model.ts
export class Upload {
$key: string;
file: File;
url: string;
progress: number;
createdOn: Date = new Date();
name: string;
constructor(file: File) {
this.file = file;
}
}

Saving additional fields on sign-up in Firebase

We're trying to work out why our information is not sending to our DB, except for email. We want to save name, surname, etc. to Firebase, but it is only saving email. What are we doing wrong?
signup.ts:
import { Component } from '#angular/core';
import { SearchPage } from '../search/search';
import { MyBookingsPage } from '../my-bookings/my-bookings';
import { NavController, AlertController, LoadingController, Loading } from 'ionic-angular';
import { Auth, User, UserDetails, IDetailedError } from '#ionic/cloud-angular';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { AuthProvider } from '../../providers/auth/auth';
import { EmailValidator } from '../../validators/email';
#Component({
selector: 'page-signup',
templateUrl: 'signup.html'
})
export class SignupPage {
public signupForm:FormGroup;
public loading:Loading;
constructor(public navCtrl: NavController, public authProvider:AuthProvider, public formBuilder: FormBuilder, public user: User, public alertCtrl: AlertController, public loadingCtrl:LoadingController) {
this.signupForm = formBuilder.group({
name: ['', Validators.compose([Validators.required])],
surname: ['', Validators.compose([Validators.required])],
birthday: ['', Validators.compose([Validators.required])],
licenseNum: [''],
email: ['', Validators.compose([Validators.required, EmailValidator.isValid])],
password: ['', Validators.compose([Validators.minLength(6), Validators.required])]
});
}
signupUser(){
if (!this.signupForm.valid){
console.log(this.signupForm.value);
} else {
this.authProvider.signupUser(this.signupForm.value.name,
this.signupForm.value.surname,
this.signupForm.value.birthday,
this.signupForm.value.licenseNum,
this.signupForm.value.email,
this.signupForm.value.password)
.then(() => {
this.loading.dismiss().then( () => {
this.navCtrl.setRoot(MyBookingsPage);
});
}, (error) => {
this.loading.dismiss().then( () => {
let alert = this.alertCtrl.create({
message: error.message,
buttons: [
{
text: "Ok",
role: 'cancel'
}
]
});
alert.present();
});
});
this.loading = this.loadingCtrl.create();
this.loading.present();
}
}
}
auth.ts:
#Injectable()
export class AuthProvider {
public fireAuth:firebase.auth.Auth;
public userProfileRef:firebase.database.Reference;
constructor(public http: Http) {
this.fireAuth = firebase.auth();
this.userProfileRef = firebase.database().ref('/userProfile'); //linked to firebase node userProfile
console.log('Hello AuthProvider Provider');
}
signupUser(name: string, surname: string, birthday: any, licenseNum: string, email: string, password: string ): firebase.Promise<any> {
return this.fireAuth.createUserWithEmailAndPassword(email, password).then( newUser => {
this.userProfileRef.child(newUser.uid).set({
name: name,
surname: surname,
birthday: birthday,
licenseNum: licenseNum,
email: email,
password: password
});
});
}
}
This is the Firebase result.
We think that the problem is in signupUser in auth.ts, but we really don't know, and we can't find anything online that fits with our structure. If possible, we don't want to have to redo our entire project!
Thanks in advance!
You should use .update() function instead of .set() function to store information at firebase database.

How to make an observer to return as observable?

I'm new to rxjs and would like some help on how to solve this.
I want to pass an Observer to onAuthStateChanged(), which takes an observer object. The observer would do some work and emit a boolean value such that the boolean value can be returned as an Observable. How do I go about implement this bridge of from observable to observer?
export class AuthGuard implements CanActivate {
constructor(private firebase: FirebaseService, private router: Router) {
}
canActivate(): Observable<boolean> {
this.firebase.auth.onAuthStateChanged(/* an observer */)
return /* an Observable<boolean> */
}
}
Since onAuthStateChanged takes an observer as input, and returns the teardown function, we can simply wrap it with:
Rx.Observable.create(obs => firebase.auth().onAuthStateChanged(obs))
Actually for strange reasons this might not work, and we can do:
var onAuthStateChanged$ = Rx.Observable.create(obs => {
return firebase.auth().onAuthStateChanged(
user => obs.next(user),
err => obs.error(err),
() => obs.complete());
})
Now if you are unfamiliar with the Observable.create function, let me explain: create takes a onSubscribe function that hands in an observer and returns the teardown function. Doesnt that sounds very familiar with now onAuthStateChanged is build up? You hand in nextOrObserver and it returns the teardown!
(Now for strange reasons nextOrObserver did not accept an observer for me, so i switched to giving it a next function instead. Hench the code above.)
With the onAuthStateChanged$ set up, we can transform the stream using operators. All operators do is transform one observable into another, and RxJs has several dozen of these. In your case, it might look like this:
canActivate(): Observable<boolean> {
onAuthStateChanged$
.do(user => {if (!user) { this.router.navigate(['/login']); } })
.map(user => !!user)
.do(user => console.log('Authenticated?', user))
}
To benefit others, here's what I ended up writing and it seems to work well.
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';
import { Observable } from 'rxjs/Observable';
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { Injectable } from '#angular/core';
import { CanActivate, Router } from '#angular/router';
import { FirebaseService } from '../shared/firebase.service';
#Injectable()
export class AuthGuard implements CanActivate {
loggedInSubject: ReplaySubject<any>;
constructor(private firebase: FirebaseService, private router: Router) {
this.loggedInSubject = new ReplaySubject(1);
this.firebase.auth.onAuthStateChanged(this.loggedInSubject);
}
canActivate(): Observable<boolean> {
return this.loggedInSubject.map(user => {
if (!user) {
this.router.navigate(['/login']);
}
console.log('Authenticated?', !!user);
return !!user;
}).take(1);
}
}
Not sure if this is necessarily 'better' than the answers above, but it's certainly cleaner. I decided to create two properties on the AuthService, one as just a boolean to reflect whether the user is authenticated, and a userLoggedIn subject which basically emits the value of the boolean property. Both properties are bound with onAuthStateChanged(). So once the state changes, the authenticated property becomes true, if authenticated, otherwise false, and userLoggedIn emits this value using next() (next(this.authenticated)). On the AuthGuard I set CanActivate() to return a boolean or Observable<boolean>. First, if the authenticated property on the AuthService is checked, and if it is returns true, otherwise it maps the userLoggedIn subject to find out whether or not the user has been authenticated. This means that after the page refreshes the guard will return the value of the emitted subject because authenticated is not yet defined, so instead just waits for userLoggedIn to return. The reason to have a check for the authenticated property first is that if you tried to change page using a nav link nothing would happen because the guard only returns the emitted value of the subject, which is only called when the state of authorisation changes - i.e. login, logout, or page-refresh (re-bootstrapping application). Code below:
AuthService
import * as firebase from 'firebase';
import { Router } from '#angular/router';
import { Injectable, OnInit } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class AuthService implements OnInit {
authenticated: boolean;
userLoggedIn = new Subject<boolean>();
constructor(private router: Router) {}
ngOnInit() {
}
checkAuthStatus() {
firebase.auth().onAuthStateChanged((user) => {
this.authenticated = !!user;
this.userLoggedIn.next(this.authenticated);
});
}
login(email: string, password: string) {
firebase.auth().signInWithEmailAndPassword(email, password).then(() => {
this.authenticated = true;
this.router.navigate(['/']);
}).catch((error) => {
console.log(error);
});
}
logout() {
firebase.auth().signOut().then(function() {
this.router.navigate(['login']);
}.bind(this)).catch((error) => {
console.log(error);
});
}
}
AuthGuard
import { CanActivate, Router } from '#angular/router';
import { Injectable } from '#angular/core';
import { AuthService } from './auth.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {
}
canActivate(): Observable<boolean> | boolean {
if(this.authService.authenticated) {
return true;
}
return this.authService.userLoggedIn.map((authenticated) => {
if(!authenticated) {
this.router.navigate(['login']);
}
return authenticated;
});
}
}
Here's the short version, a helper function you can place anywhere...
export function MakeAuthstateObservable(
auth: firebase.auth.Auth
): Observable<firebase.User> {
const authState = Observable.create((observer: Observer<firebase.User>) => {
auth.onAuthStateChanged(
(user?: firebase.User) => observer.next(user),
(error: firebase.auth.Error) => observer.error(error),
() => observer.complete()
);
});
return authState;
}
Similar approach:
./auth-guard.ts
import { Injectable } from '#angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { Observable } from 'rxjs/Observable';
import { AuthService } from '../shared/auth.service';
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private authService: AuthService) { }
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
return this.authService.authState.map((auth) => {
if (auth == null) {
this.router.navigate(['auth']);
return false;
} else {
return true;
}
}).first();
}
}
./shared/auth.service.ts
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import { FirebaseApp } from '../shared/firebase';
#Injectable()
export class AuthService {
public auth: firebase.auth.Auth;
public authState: Observable<firebase.User>;
constructor(public app: FirebaseApp) {
this.auth = app.auth();
this.authState = this.authStateObservable(app);
}
/**
* #function
* #desc Create an Observable of Firebase authentication state
*/
public authStateObservable(app: FirebaseApp): Observable<firebase.User> {
const authState = Observable.create((observer: Observer<firebase.User>) => {
this.auth.onAuthStateChanged(
(user?: firebase.User) => observer.next(user),
(error: firebase.auth.Error) => observer.error(error),
() => observer.complete()
);
});
return authState;
}
}
./shared/firebase.ts
import * as firebase from 'firebase';
export class FirebaseApp implements firebase.app.App {
name: string;
options: {};
auth: () => firebase.auth.Auth;
database: () => firebase.database.Database;
messaging: () => firebase.messaging.Messaging;
storage: () => firebase.storage.Storage;
delete: () => firebase.Promise<any>;
constructor() {
return firebase.initializeApp({
apiKey: 'AIzaSyC6pDjAGuqXtVsU15erxVT99IdB0t4nln4',
authDomain: 'inobrax-ebs-16552.firebaseapp.com',
databaseURL: 'https://inobrax-ebs-16552.firebaseio.com',
storageBucket: 'inobrax-ebs-16552.appspot.com',
messagingSenderId: '383622803653'
});
}
}

Resources