I am using Angular 8 and have a form where a user can choose what he wants to query the database for and then click either of two buttons - one to view data in realtime on the website, and the other to download the data.
I thought I could make use of one function to make a query and then call different functions depending on what button the user clicked, using get() for the download and valueChanges() for the realtime data view. But when I try this, I get the following errors in the browser console. (This is with query as type any - if I specify the type as AngularFirestoreCollection I get errors regarding my type for the get() part in VSCode)
ERROR Error: "Uncaught (in promise): TypeError: this.query.get is not
a function
I can add that I previously had two completely separate (working) functions for downloading and viewing in realtime. And for downloading I used the below query. I gather this is actually a Firestore Query, whereas the "query" I'm trying to use in my updated code is an AngularFirestoreCollection. But is there a way I can make some kind of Query/Collection that will work for both get() and valueChanges()?
Old (working) query:
var query = this.afs.collection(collection).ref.where('module', 'in', array_part);
Trying a common function makeQuery():
onSubmit(value, buttonType): void {
if (buttonType=='realtime') {
this.getRealTimeData(value);
}
if (buttonType=='download') {
this.downloadCsv(value);
}
}
async downloadCsv(value) {
this.query = this.makeQuery(value);
this.dataForDownload = await this.getDataForDownload();
this.dataForDownload = JSON.stringify(this.dataForDownload['data']);
console.log('Data: ', this.dataForDownload);
var date = new Date();
var date_str = this.datePipe.transform(date, 'yyyy-MM-ddTHH-mm');
this.makeFileService.downloadFile(this.dataForDownload, 'OPdata-' + date_str);
}
getDataForDownload() {
return this.query.get()
.then(function (querySnapshot) {
var jsonStr = '{"data":[]}';
var dataObj = JSON.parse(jsonStr); //making object we can push to
querySnapshot.forEach(function (doc) {
JSON.stringify(doc.data()), ', id: ', doc.id);
dataObj['data'].push(doc.data());
});
return dataObj;
})
.catch(function (error) {
console.log("Error getting documents: ", error);
});
}
async getRealTimeData(value) {
this.query = await this.makeQuery(value);
this.data = this.query.valueChanges();
}
async makeQuery(value) {
var collection: string;
return this.query = this.afs.collection<DataItem>('CollectionName', ref => ref.where('datetime', '>=', '2020-01-15T09:51:00.000Z').orderBy('datetime', 'desc').limit(100));
}
The valueChanges() is a method used in angularfire to retrieve data from firestore, while the get() method is used to retrieve from firestore but using the vanilla javascript.
Mixing both methods will return an error as you have seen in your code. Therefore, since angularfire was created above the javascript firebase code, then you should be able to use valueChanges() to view data in realtime on the website, and to download the data.
I'm using react-native-firebase in my app. The problem i'm facing is how to handle the UI updates when user tries to push data when offline.
If the user is online we can use the on() method to get realtime updates but what to do when they are offline. We know that the pushed data is stored in the cache and pushed when user is online again. Can this cached data be used to do what i aim at achieving?
Here's the code i used to receive realtime updates:
var ref333 = firebase.database().ref(`/user-posts/${uid}/`)
ref333.on('value',function (snap) {
var s = snap.val();
console.log("NEW POSTS "+JSON.stringify(s))
})
The code i use to push the data.
var postData = {
uid: uid,
body: 'body',
title: 'title',
starCount: 0
};
// Get a key for a new Post.
var newPostKey = firebase.database().ref().child('posts').push().key;
var ref222 = firebase.database().ref(`/posts/${newPostKey}`)
var ref333 = firebase.database().ref(`/user-posts/${uid}/${newPostKey}`)
ref222.push(postData, function (onComplete) {
console.log("COMPLETED")
ref333.push(postData,function (onComplete) {
console.log("NEXT COMPLETED")
}, function (error) {
console.log("ERROR IN ",error)
})
}, function (error) {
console.log("error == "+error)
})
The .on snspashot listener should be triggered even if in offline mode.
According to the docs:
https://firebase.google.com/docs/database/web/read-and-write
You can use the value event to read a static snapshot of the contents
at a given path, as they existed at the time of the event. This method
is triggered once when the listener is attached and again every time
the data, including children, changes.
This should work in offline mode as well. If you are not receiving updates - something else is wrong.
This problem was solved by adding this lines of code to your native code:
https://rnfirebase.io/docs/v5.x.x/core/default-app#Enable-Database-Persistence
I have my DB in Firebase but I want to do a WHERE, as a login (i.e. if I enter the user and the password, it brings me the user's corresponding data).
This is my DB in firebase:
An example:
When I send the email and telefono to tb01_usuario bring me the corresponding data:
Currently I am doing it only with the ID that is similar to the field telephone, but I need to do it with the two fields that are inside each record, I hope they understand me
verifica_usuario(telefono: string,email : email ) {
email = email;
telefono = telefono;
let promesa = new Promise((resolve, reject) => {
//this.items = afDB.list('/cuisines');
this.af.list('/tb01_usuario/' + telefono + email )
.subscribe(data => {
if (data.length === 0) {
//clave no es correcta
resolve(false);
} else {
//clave correcta
this.telefono = telefono;
this.guardar_storage();
resolve(true);
}
});
})
.catch(error => console.log("ERROR en promesa Service: " + JSON.stringify(error)));
return promesa;
}
You can't do that directly in Firebase.
As per Firebase docs
The Realtime Database API is designed to only allow operations that can be executed quickly.
You should structure your data in a way that you can easily fetch what you need.
What you might want to use is either redundant nodes (similar to what MarianoCordoba mentioned), which is replicating data in a way that makes it easier to access what you need, or indexOn (documentation) and orderByChild([CHILD_NODE]).equalTo([QUERY]) (documentation). Note that you can also use orderByKey() and orderByValue(), which allow you to filter/sort your data (which is what I'm assuming you mean by using WHERE in Firebase).
I am using AngularFire2 with ionic 2 and storing the data in webSqlStorage.
When I complete the the first GET it saves it to storage. What I want to do is if the user has a connection then check the data from Firebase, if it has not changed, then get the local data, else get the updated data.
Currently, I have a simple check to see if there the storage is not null which then gets the local data, but this will not work for production.
let loader = this.loadingCtrl.create({
content: 'Getting Programs...'
});
loader.present().then(() => {
this.storage.get('programs').then((data) => {
if (data != null) {
loader.setContent("Getting Local Data...");
this.programs = data;
} else {
this.yApi.getPrograms().then(data => {
this.programs = data;
this.storage.set('programs', data);
},err => {
// Probaly offline with no local data
console.log("Err is to human");
});
}
}).then(() => {
loader.dismiss();
});
});
Just wondering if there is a way to write something like
if (data != null || this.af.checkUpdated('/programs')) { ...
or something.
I used an additional database value like program_version in my projects. If firebase program_version is greater than the local storage, than update. On every program update your program_version will be increased or set a timestamp.
I'm working on a firebase+angularjs app and I'm using the simple email and password authentication and it's working properly.
I'm just wondering if I can add extra user data on the user table which is being used by firebase email+password auth, like I want to add billing info and other details concerning the user without creating extra node/table on firebase to store these extra data.
Firebase stores the email/password users in a separate location, that you don't have direct access to. You cannot expand the data in this location.
Since many application developers want to access the user data in their application code, it is a common practice to store all users under a /users node inside the application database itself. The disadvantage is that you have to do this yourself. But the positive side of this is that you can store any extra information if you want.
See the Firebase guide on storing user data for sample code. From there:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
ref.onAuth(function(authData) {
if (authData && isNewUser) {
// save the user's profile into Firebase so we can list users,
// use them in Security and Firebase Rules, and show profiles
ref.child("users").child(authData.uid).set({
provider: authData.provider,
name: getName(authData)
});
}
});
NOTE: This method only works if you are using Firebase Admin SDK and you need to have end point on your server to manage custom tokens
Firebase Admin SDK has an option to create custom tokens with additional claims object, which can contain arbitrary data. This might be useful to store some user related info, like whether the user is premium user or not.
Additional claims data is accessible using auth object.
example
var uid = "some-uid"; //this can be existing user UID
var additionalClaims = {
premiumAccount: true,
some-user-property: 'some-value'
};
admin.auth().createCustomToken(uid, additionalClaims)
.then(function(customToken) {
// Send token back to client
})
.catch(function(error) {
console.log("Error creating custom token:", error);
});
additionalClaims are also accessible in Firebase security rules.
for more info read Firebase Custom Tokens
A Firebase User has a fixed set of basic properties—a unique ID, a primary email address, a name and a photo URL—stored in the project's user database, that can be updated by the user (iOS, Android, web). You cannot add other properties to the Firebase User object directly; instead, you can store the additional properties in your Firebase Realtime Database.
Firebase has a fixed set of user properties which can be updated but not added on to.
However you can add small amounts of data with the help of serialization and deserialization using JSON.stringify() and JSON.parse()
And then use any one of the unused properties to store the string
either in DisplayName, or photoURL property.
Keep in mind the data that can be added has to be small in size and stored as a string.
And this can be only possible with using the method in the FIREBASE SDK and not the angularfire as illustrated below
var user = firebase.auth().currentUser;
user.updateProfile({
displayName: "Jane Q. User",
photoURL: "https://example.com/jane-q-user/profile.jpg"
}).then(function() {
// Update successful.
}, function(error) {
// An error happened.
});
You could store more json like data in the photoURL or displaYName variable in the form of string here.
My answer is not angular related but I searched quiet a bit to find out how to do it using Polymer and Polymerfire so I add this answer to help people get it done faster than i did.
I had to add a separate node to db as Frank van Puffelen mentioned.
Imports:
<link rel="import" href="../bower_components/polymerfire/firebase-app.html">
<link rel="import" href="../bower_components/polymerfire/firebase-auth.html">
<link rel="import" href="../bower_components/polymerfire/firebase-document.html">
Then place anywhere in your app a <firebase-app> component:
<firebase-app
name="yourAppName"
api-key= "{{yourApi}}"
auth-domain= "{{yourAuthDomain}}"
database-url= "{{yourDbUrl}}"
>
</firebase-app>
After that you will need to use <firebase-auth> and <firebase-document>:
Template :
<firebase-auth
id="auth"
app-name="yourAppName"
signed-in="{{signedIn}}"
user="{{user}}">
</firebase-auth>
<firebase-document
id="document"
app-name="yourAppName"
path="{{usersPath}}" // e.g "/users"
data="{{userDocument}}">
</firebase-document>
Script:
this._register = function(){
var formValid = this.querySelector('#register-form').validate();
var auth = this.querySelector('#auth');
if(formValid && this.passWordsIdentic){
//The actual registration
auth.createUserWithEmailAndPassword(this.email, this.password).then(function(user){
console.log('auth user registration succes');
//Example values
this.userDocument.uid = user.uid;
this.userDocument.email = user.email;
this.userDocument.firstName = this.firstName;
this.userDocument.lastName = this.lastName;
this.userDocument.userName = this.userName;
this.$.document.save(this.usersPath).then(() => {
console.log("custom user registration succes");
this.$.document.reset();
});
}.bind(this)).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
console.log('error: ', errorCode);
);
}
}
And that's it, you may want to take a look at this excellent google codelab which is a good introduction into using firebase with polymer.
Here is the code of registration where add the extra fields in the Users table
import { AngularFireAuth } from "#angular/fire/auth";
constructor(private firebaseAuth: AngularFireAuth){}
registration(data: any, password: any) {
return this.firebaseAuth.auth.createUserWithEmailAndPassword(data.Email, password)
.then(res => {
res.user.updateProfile({
displayName: `${data.DisplayName}`
})
data.UserId = res.user.uid;
data.PhoneNumbers = [{
NumberType: '',
NumberValue: ''
}];
data.PhotoUrl = '';
data.Addresses = [{
AddressLine1: '',
AddressLine2: '',
City: '',
State: '',
Country: '',
PostalCode: '',
AddressType: ''
}];
data.IsDeleted = false;
this.fireStore.doc(`users/${res.user.uid}`).set(data);
this.toastr.success('User has been register successfully!', 'Successfull!');
return true;
}).catch(err => {
switch (err.code) {
case 'auth/email-already-in-use':
this.toastr.error(`Email address ${data.Email} already in use.`, 'Error!');
break;
case 'auth/invalid-email':
this.toastr.error(`Email address ${data.Email} is invalid.`, 'Error!');
break;
case 'auth/operation-not-allowed':
this.toastr.error('Error during sign up.', 'Error!');
break;
case 'auth/weak-password':
this.toastr.error('Password is not strong enough. Add additional characters including special characters and numbers.', 'Error!');
break;
default:
this.toastr.error(err.message, 'Error!');
break;
}
});
}
Here's a swift version. Your user structure ("table") is like
--users:
-------abc,d#email,com:
---------------email:abc.d#email.com
---------------name: userName
etc.
After you pass the auth FIRAuth.auth()?.createUser you can set the users in database as below:
let ref = FIRDatabase.database().reference()
let rootChild = ref.child("users")
let changedEmailChild = u.email?.lowercased().replacingOccurrences(of: ".", with: ",", options: .literal, range: nil) // Email doesn't support "," firebase doesn't support "."
let userChild = rootChild.child(changedEmailChild!)
userChild.child("email").setValue(u.email)
userChild.child("name").setValue(signup.name)
Please note that method is changed in v4.0.0. Therefore, you need to use the below code to retrieve the user profile:
afAuth.authState.subscribe((user: firebase.User) => {
this.displayName = user.displayName;
this.email = user.email;
this.photoURL = user.photoURL;
});
The answer from Frank is good, but things are a little different in Angular6/Firebase5/Angularfire5:
Here is my click handler for signing in a user:
this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then((e) => {
console.log("Log-In Success" + e.additionalUserInfo.profile.name);
if (e.additionalUserInfo.isNewUser)
this.addUserToDatabase(/*...*/);
}).catch((error) => {
console.log("Log-In Error: Google Sign-In failed");
});