You can up your UX game by adding things like skeleton loaders, loaders, progress bars to async operations. For example uploading files to a server might take time due to network connection or file size. We can also Monitor Firebase upload progress using @angular/fire package. And that is what we gonna do in this article.
If you are more of a video person that I have also attached the video at the end of the article
Watch VideoFor the final source code you can visit the Github repo
GithubThe end result of our app would look like as below
This article has a loose dependence on last two articles where I have discussed how to create a simple todo app with firebase and than implement firebase storage. You can read them to know about the journey or continue with this article as a standalone tutorial.
The starting point of our project is fire storage branch ionic ng todo github branch. You can switch to upload progress branch to get the final result.
The Markup
We will go with the existing markup. We do need some css generated content that is covered next
The CSS
We need to style the loader but question is which one. Ion-loading accepts a cssClass property. We can use that to give it our own class. Let’s add that in our code
async presentLoading() {
this.loading = await this.loadingController.create({
message: 'Please wait...',
cssClass: 'with-progress'
});
return this.loading.present();
}
Ionic loading component is part our component but not in the dom. It exist somewhere else in the scope of the project. It can be confirmed below. That’s why we can’t style it in our HomePage component.
The styles should go in global.scss
for this purpose. CSS pseudo-elements can be used to add content to markup without touching HTML. Especially in our case, for aesthetic purpose.
.with-progress {
.loading-wrapper{
position: relative;
&::after {
content: "";
position: absolute;
width: var(--percent-uploaded);
height: 5px;
background-color: var(--ion-color-primary);
bottom: 0;
left: 0;
transition: width 0.5s linear;
}
}
}
Remember the days of designing whole layout was built using absolute divs. Somewhat same is happening is here. The CSS generated content here is the bar that shows progress. It’s is absolutely positioned at the bottom of ion-loading. All the magic is happening on the width property. It uses css custom property which start with zero and updated dynamically by JavaScript
Let’s code the firebase upload progress
Most of the modifications are happening in one method i-e uploadFile
.
async uploadFile(id, fileList): Promise {
if(fileList && fileList.length) {
try {
await this.presentLoading();
const file = fileList[0]
const task = this.storage.upload(`images/${id}`, file)
task.percentageChanges().subscribe(resp => {
this.loading.style.setProperty('--percent-uploaded', `${resp.toFixed()}%`)
})
} catch (error) {
// ...
}
} else {
// ...
}
}
The highlighted lines show we are using the upload
method of firebase storage to monitor firebase upload progress. We have gave up the put
method so let’s adjust accordingly.
Next task percentageChanges
method does all the heavy lifting. We just listen to it and update the –percent-uploaded CSS custom property of ion-loading. Remember in the CSS section, this property is inherited by it’s ::after pseudo-element. Hence we style that blue bar to update on the UI.
There is one more thing in our uploadFile method. We need to return the promise when we are done with the upload task. So that we can resume our program execution.
try {
// ...
return task.snapshotChanges().pipe(
finalize(() => this.loading.dismiss() )
).toPromise()
// ...
}
The getDownloadURL
logic is now moved to addTodo method.
addTodo(){
this.itemsRef.add({
// ..
})
.then(async resp => {
const uploadTask: UploadTaskSnapshot = await this.uploadFile(resp.id, this.selectedFile)
const imageUrl = await uploadTask.ref.getDownloadURL()
this.itemsRef.doc(resp.id).update({
// ..
})
})
}
This is last change we have made to achieve firebase upload progress. Most of the above code is quite self explanatory. The UploadTask returns a promise which can be queried to get downloadURL.
Final Words
The good thing that we have achieved is the nondestructive feature addition. I have learned how well we can add features to existing code without breaking. Thanks to this Refactoring Book.