Categories
Firebase Java Script Screencast Tutorials

Upload Photos to Firebase Storage with Ionic Angular [Screencast]

Firebase storage or cloud storage for Firebase makes it super easy to store your photos and videos. Firebase is Google platform known for scalability, cost effectiveness and security.

It is reliable as it based on Mobile First approach. Which means it intelligently pauses and resumes the file transfer based on internet connectivity.

If you are more of a video person that I have also attached the video at the end of the article.

For the final source code you can visit the Github repo

How often we see network failure when 90% file is uploaded and 10% left. We have to start the process all over again. Which not only wastes our time but bandwidth as well. That’s where Firebase Storage shines.

Without further ado let’s gets cracking.

Setting up firebase project

I assume you already know how to set up a brand new firebase project. I wrote a nice article about Setting up a project for firebase storage. The article is quite thorough but I have linked to the exact section.

One more thing we need to do is to setup a folder for storage (which is also called in the server less technologies). From the menu click on the storage and then click on the the big Get Started button on the right.

Firebase Storoage for Angular Getting Started page from technbuzz.com
Welcome screen telling about the firebase Storage and all its intricacies

After clicking on getting started button, a firebase sets some rules and initialise a bucket that holds our storage. The initial rules are quite fragile in other words; quite insecure. A proper authentication and stricter rules are recommended for app in production.

Create a new folder, give it a name which will be used as a reference later. Although firebase storage holds every kind of files. I will name the folder images.

For the source code I will be using firebase-storage branch of our GitHub repository. Firebase is already installed in this project with some nice to do list. If you need a refresher just go through those steps.

Just clone the repo, add your environment file (that holds the firebase config secret strings) and spin up the server.

What are we building

If was little hard for me to choose what kind of application. The most obvious one is to build the gallery. Which requires to create more view. For the sake of simplicity I went with the much easier one.

With the existing to do list I added an option for the image. We can add a todo without the image as well. The rest of it is shown in following videos

[118 kb] Click the play button to load the video

Start with the Markup

The markup consists of two parts, listing and a from element to submit new todo. Listing uses ion-list with ion-item. We will introduce ion-thumbnail component to display the image if it exists. The ion-item becomes

    <ion-item *ngFor="let item of items | async">
      <ion-thumbnail slot="start" *ngIf="item.imageUrl">
        <img [src]="item.imageUrl">
      </ion-thumbnail>
      <ion-label>{{ item.title }}</ion-label>
      <ion-button slot="end" fill="clear" color="danger" (click)="remove(item)">
        <ion-icon slot="icon-only" name="trash"></ion-icon>
      </ion-button>
    </ion-item>

We also need to modify the form element. Input of type file can be used to upload image to firebase storage.

    <ion-card>
    <ion-card-header>
      <ion-card-subtitle>Add New Todo</ion-card-subtitle>
    </ion-card-header>

    <ion-card-content>
      <form class="ion-padding" method="POST" (submit)="addTodo()">
        <ion-item>
          <input type="file" name="inputFile" (change)="chooseFile($event)" required>
        </ion-item>
    
        <ion-item lines="none">
          <ion-input required name="todo" [(ngModel)]="newTodo" placeholder="Todo title"></ion-input>
        </ion-item>
    
        <ion-button type="submit">Add</ion-button>
      </form>
    </ion-card-content>
  </ion-card>

The reason we are listening the change event of input file is to grab the selected file. We can’t just bind the value like we are used to (banana in a box ring a bell ?). More explanation will come later in code part. The Ionic card component is used in great to differentiate the new item form.

A bit of Styling

Just curve the corners using CSS custom properties and some white space for breathing room.

    
ion-thumbnail{
  --border-radius: 8px;
}

ion-card{
  margin-top: 7rem;
}

Code Behind Firebase Storage

We are done with markup and sprinkle of style. We also need to modify the addition and removal of item to adjust the new requirements. But first of all let’s include some new modules to app.module.ts

    import { AngularFirestoreModule } from '@angular/fire/firestore';
import { AngularFireStorageModule } from "@angular/fire/storage";

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, 
    AngularFireModule.initializeApp(environment.firebase), 
    AngularFirestoreModule,
    AngularFireStorageModule
  ]
})

After we have added the modules, next to wire up the actual functionalities.

    import { AngularFireStorage } from '@angular/fire/storage';

@Component({})
export class HomePage {
  items: Observable;

  newTodo: string = '';
  itemsRef: AngularFirestoreCollection;

  selectedFile: any;
  constructor(private db: AngularFirestore, private fireStorage: AngularFireStorage) {
    this.itemsRef = db.collection('items')
    this.items = this.itemsRef.valueChanges();
  }

  chooseFile (event) {
    this.selectedFile = event.target.files
  }
}

In above code snippet we have injected AngularFireStorage to the constructor. From line 16-19 is the function that is hooked to the input[file] in the markup. This function just sets the local variable with the selected file.

Understand the flow of firebase storage

A little explainer will make things easier to follow code. At first we need to upload the file to storage. In order to access the file later, we need the downloadUrl of that file.

    addTodo(){
    this.itemsRef.add({
      title: this.newTodo
    })
    .then(async resp => {
      
      const imageUrl = await this.uploadFile(resp.id, this.selectedFile)
      
      this.itemsRef.doc(resp.id).update({
        id: resp.id,
        imageUrl: imageUrl || null
      })
      
    }).catch(error => {
      console.log(error);
    })
  }

  async uploadFile(id, files):Promise {
    if (files && files.length) {
      try {
        const task = await this.storage.ref('/images').child(id).put(files[0])
        return this.fireStorage.ref(`images/${id}`).getDownloadURL().toPromise()
      } catch (error) {
        console.log(error);
      } 
    }
  }

Firebase returns document id when it’s created. We will use this id as a reference name of the accompanied image. On line 7 we are providing id and selected to the uploadFile method.

The uploadFile methods check whether user has selected the file from file dialog or it was just cancelled. Next on line 22 we are grabbing the reference of our folder i-e images. The child method sets the name of our new file (which is the document id in this case). The put method uploads the file.

After file finished uploading, than we need to grab it’s downloadUrl and just return it. Coming back to line 7, now we have the image url which is reused when we update document id. If user cancels the file dialog or just ignore the image than fileUpload will return undefined. In other words that particular todo item will not have the image. But if user has selected the file than we update the todo item id along with imagUrl.

While all the code in place, we have a choice add image to our new to do item. There is one more thing to do; what happens when we remove the todo item.

Remove Image from Firebase Storage

As we already have the reference of the image thanks to the id of associated todo list. All we have to do is to call remove method firebase storage

    remove(item){
    if(item.imageUrl) {
      this.storage.ref(`images/${item.id}`).delete()
    }
    this.itemsRef.doc(item.id).delete()
  }

It is quite identical to uploading the file. We fetch the reference and call the remove method, only, if item has an image.

We have achieved the functionality up to this point. But uploading image is an asynchronous operation on its own. Which means it will take time, it will happen at some point in the future. The good thing is to let user know that something happens behind the scenes.

Improve UX

Let’s enhance the uploading with the ionic loading component.

    import { LoadingController } from '@ionic/angular';

constructor(private db: AngularFirestore, 
    private storage: AngularFireStorage, 
    private loadingController: LoadingController
    ) {
   //...
  }

As we have injected the Loading Controller to the constructor.

    async uploadFile(id, file): Promise {
    if(file && file.length) {
      try {
        await this.presentLoading();
        const task = await this.storage.ref('images').child(id).put(file[0])
        this.loading.dismiss();
        return this.storage.ref(`images/${id}`).getDownloadURL().toPromise();
      } catch (error) {
        console.log(error);
      }
    }
  }

  async presentLoading() {
    this.loading = await this.loadingController.create({
      message: 'Please wait...',
      duration: 2000
    });
    return this.loading.present();
  }

The above code will present the loading indicator wait for operation and dismiss it when we are done with it.

Conclusion

One nice thing about this enhancement is that it is a non destructive addition to to do items. We can choose to upload image when adding todo or skip the image selection.

Mention in the comment, what awesome software you are going to build using firebase storage. Share this article with your colleagues.

The source code is available on the github

Screencast