Account Management using Cloud Firestore in Angular
Extending your authentication process with user settings
This is the second article being part of a series exploring new ways of developing all the core building blocks of a Single Page Application with Angular and Firebase.
Once you got the authentication process covered, you’ll face the need of giving your users a profile they can edit and share publicly (at least part of it) together with the possibility of managing their account including permanently deleting it.
Following up on the AuthService described in the previous article of this series, in this article we’ll explore a way of storing a customizable user profile in Cloud Firestore to be easily accessed across the application.
Here we’ll be covering:
- DatabaseService: the database service built on AngularFirestore service from AngularFire.
- DatabaseDocument: a class representing a document object from the database leveraging on AngularFirestoreDocument.
- UserProfile: a service embodying the user profile by extending the DatabaseDocument.
- SettingsModule: the actual module implementing the user settings page.
As for the previous article, all of the above will be part of the live demo available on StackBlitz you’ll find the reference of at the bottom of this article.
As you may remember from the previous article, I support the approach of implementing our own core services, so, to better serve our application needs and that’s what we are going to do with our DatabaseService:
Our service mimics AngularFirestore but instead of simply wrapping it, this service gets down to the original firestore api, so, to introduce additional features such as batch writes and transactions we’ll be needing in the upcoming articles.
Besides, I’m a huge fan of AngularFire library and the way they implemented Observable based data synchronization; I still prefer to rely on the original Promise based firestore api when it comes to perform “single pointed” operations like get(), delete() and so forth.
So the basic idea here, is that our implementation is going to pick the best of both worlds :)
The most relevant function, for what concerns the subject of this article, is
document() returning an instance of a document object within the database.
The DatabaseDocument class looks like purely wrapping the DocumentReference (here renamed dbDocumentRef) from the firestore api:
But don’t get fooled… we are not reinventing the wheel. By looking closely to the
stream() method you’ll see we are actually taking advantage from AngularFirestoreDocument
snapshotChanges() method to stream the document content as it changes in the database.
The original observable content is than mapped into the expected data format. In fact, as you may have noticed, DatabaseDocument is a Generic class using the T argument to type the document content with an interface of your choice extending
dbCommon interface is the basic document data type we use to “spice up” the basic functionalities of DatabaseDocument so that every time we write into the database the document automatically gets a
updated timestamp for calling
update() method respectively.
upsert() method is actually checking for the document existence prior to set or update the content consequently.
Finally, everytime we read the document content we get to read the
id as well, so, we’ll always have the document
uid at our disposal.
The key piece of this article is the UserProfile service extending the DatabaseDocument functionalities to embody the authenticated user profile:
As you see from the code above, UserProfile takes advantage from all the functionalities implemented by DatabaseDocument.
stream() method is overridden to resolve the authenticated user first, so, to stream the profile content from a document named as the user
In the constructor the base class is initialized with a null reference than replaced by the effective document reference by
stream() as soon as the current user
uid is known.
A snapshot of the profile content is persisted in the variable
data making sure the document reference is always up to date.
Finally, there’s an helper method
register() to initialize the profile content with the user object account information when a new user is registered.
For the settings page I decided to go with children routes, so, the main SettingsComponent page is pretty simple letting the user navigate between the ProfileComponent and the AccountComponent or to eventually sign-out:
So you see the component implements a MatNavList on the left rendering the child component on the right while it directly calls the AuthGuard
disconnect() method to sign-out.
The specific routes are than defined in SettingsModule:
So that ProfileComponent or AccountComponent is rendered accordingly in the child
The profile page displays the profile data in a form for the user to modify it:
Is in the constructor of the ProfileComponent where the form is built and its content patched loading it from the profile:
save() method is the one called to update the profile marking the form back to pristine when done.
The second page let the user manage the account functionalities:
The component purely implements UI functionalities relying on the AuthGuard service to perform the task behind the scenes:
Particularly, since updating the account credentials or deleting the account completely requires the user to have signed in recently, this implementation uses AuthGuard to prompt the user for re-authentication providing both a convenient way to ask for confirmation and to refresh the authentication token.
Updated Login Component
Since we now extended the demo app with UserProfile we have to make sure a profile is created every time a new user is registered:
So the LoginComponent
registerNew() method has been updated accordingly.
Similarly, prior to delete the user, we better delete her profile document as well:
And that’s the perfect task to be done from within
At this point in our journey we are almost done with user management. A last piece of this puzzle would be enabling our users uploading their own profile picture. We’ll see how this can be implemented in the next article of this series.
A fully functional demo of the app implementing the code described here is available live on StackBlitz here: