Relaying upload progress engages user attention.  Assembling a few Angular Material components and using Firebase storage, the UI can present the ability to start, pause, cancel, and display progress of a file upload.

If you recall the days before the SPA pattern (Single Page Applications) emerged, the traditional method for uploading files posted an entire form.  The form wrote the file content to the request stream leaving the browser to appear hanging.  There's no difference in the request today, but no simple method existed to relay the progress of the upload back to the user.

Browser capabilities advanced in the rise of XHR (XMLHttpRequests) and AJAX (Asychronous JavaScript and XML) allowing HTTP requests to be made between browser (client side code) and server.  These two advancements opened the ability to observe progress of the request enhancing the user experience.

In this tutorial, you'll learn how to use Angular Material Components that upload files to Firebase.

Goals

1. Model a File Upload Interface and Upload Service Contract

2. Create a Firebase Upload Service

3. Implement a SCAM File Upload Component and Upload List Component

4. Configure the Upload Service Module and AngularFire for Firebase

5. Run the Application and Upload Files

To follow along in this tutorial, use the GumRoad link below.

Free Your Developers

Nx Monorepo Starter alleviates developers from re-inventing deployments on popular Google Cloud Services, standardizes on libraries, and saves time for scaffolding projects.

Download

Create a File Upload Interface and Service Contract

The FileUpload contains information about the file chosen and the status of the HTTP request.

Property Name Type Description
id string A unique identifier for the file upload
name string Name of the chosen file
fileData File Provides information about files and allows JavaScript in a web page to access their content.
percentageUploaded number The percentage the upload is complete
status FileUploadStatus The status of the file upload
contentType string The content type of the file e.g. image/png
path string? Optional path value for where the file will be stored on the server

The FileUploadStatus enumerates the status of the file upload including QUEUED, PAUSED, CANCELLED, IN_PROGRESS, COMPLETE, and ERROR.

The upload service contract provides flexibility for choosing an implementation.  Firebase is specific to Google Cloud, so changing to a different cloud provider, say Amazon's S3 storage, would involve creating another UploadService that implements the IFileUploadService interface.

With different UploadService implementations, swapping the dependency eases the pain of changing a specific reference as we can inject an IFileuploadService interface.  You'll learn to configure this in the Implement File Upload Components and the Configure Upload Service Module sections.

Property / Method Description
fileUploads$: Observable<Record<string, FileUpload>> An observable property that keeps reference of files uploads by their id
upload(fileUploadId:string):void Starts an upload by a specific fileId
addFiles(fileList: FileList):void Adds files to the object of fileUploads$
remove(fileUploadId: string):void Removes the file from the object of fileUploads$

Create Firebase Upload Service

The FirebaseUploadService handles uploading files to Firebase.

The AngularFireStorage class injects into the constructor from the @angular/fire package.  AngularFireStorage handles the upload into Firebase.  Below, I explain the properties and methods of the FireUploadService.

Property Name Access Modifier Type Description
tasks private Record<string, AngularFireUploadTask> Keeps track of the AngularFireUploadTasks by object property id. This id maps to a FileUpload.id to access the asks easily in the case to pause, resume, or cancel the upload.
queudedFiles$ private BehaviorSubject<Record<string,FileUpload>> Keeps track of all the files
fileUploads public/ readonly Observable<Record<string, FileUpload>> Readonly object of FileUploads
Method Desciption
addFiles(fileList:FileList): void Transforms files to the FileUpload type and adds them to the queuedFiles$
upload(fileUploadId: string):void) Creates the task in AngularFireStore service and subscribes to the task's snapshotChanges. Updates the queuedFiles$ status changes and percentage completed.
remove Removes a file from the queue and cancels any tasks that may have been running.

The upload method finds the file by it's id in the queuedFiles$ property.  Using the path and fileData, fileStorage creates an AngularFireTask by calling the upload method.  The generated task appends to our tasks property, and the fileUpload's status updates to IN_PROGRESS.

The task.snapshotChanges is an observable that emits the changes in state of the uploading file.  The filter operator in this case checks if the value is notEmpty and primarily I added this for testing purposes.  The map operator transforms the value emitted to a FileUpload.  Once the observable completes, the finalize operator removes the current task from the tasks property.

Subscribing to changes invokes the upload.  As values are emitted, the queuedFiles$ property updates the fileUpload in it's dictionary.

Implement File Upload Components

A couple of SCAM (Single Component Angular Module) modules drive consistency in display and upload service.

The NgServeFileUploadModule imports the MatProgressModule to support the FileUploadComponent presentation component.

The FileUploadComponent consumes one @Input(), fileUpload.  It's responsible for displaying the progress, file name, and percentage upload to the user.  The mat-progress-bar reads the fileUpload.percentageUploaded to reflect upload progress.

The FileUploadListModule follows the same SCAM pattern.  The FileUploadListComponent depends on the MatButtonModule and the MatIconModule and are added to the imports of the FileUploadListModule.

The FileUploadListModule provides flexibility for you to choose which implementation of the IFileUploadService to consume in the FileUploadListComponent.  The static method configure defines the IFileUploadService to be injected into the FileUploadListComponent.  

The FileUploadListComponent orchestrates the user's actions having them choose files to upload, and start / remove uploads.

In the markup of the FileUploadListComponent, a list of the files selected displays each file for upload using the ng-serve-file-upload component.  Underneath, two mat-icon-buttons empower the user to start an upload or remove the file from the list.

Toward the bottom of the markup, styling the file input involves hiding the element and invoking the click method from an Angular Material button.  On the file input, notice the identifier #fileListUpload.  When the Choose Files material button is clicked, the #fileListUpload's click event invokes in the component typescript chooseFiles method.

The FileUploadListComponent fileListUpload property exposes an ElementRef to the #fileListUpload file input.  So when the Choose Files material button click event fires, the chooseFiles method invokes the click event on the #fileListUpload file input.

Property Description
fileListUpoad:ElementRef Makes a reference to the file input in the component
UPLOAD_STATUS A reference to the FileUploadStatus to compare the file status to display / hide buttons.
fileUploads$ The files stored in state of the IFileUploadService
fileTypes An @Input() that controls file selection e.g. images/* as defined by the consuming component
Method Description
chooseFiles Invokes the fileListUpload click event to open the list of files to choose
filesSelected An event handler for the change in files from the fileListUpload(evt:Event). Adds files to the fileUploadService
uploadFile(fileUploadId: string) Invokes the upload for a particular file
remove(fileUploadId: string) Removes the file state of the file upload service

Configure Upload Service Module

Configuring the upload service empowers you to choose the implementation of a file upload service.  Recall this pattern offers flexibility for you swap upload services that implement the IFileUploadService contract.

Below the NgServeIoFeatureModule configures the FirebaseFileUploadService.

The application module lazy loads the  NgServeIoFeatureFormModule through route configuration via the forms route.  Ensure that the AngularFireModule initializes the app with your Firebase web configuration options.  Below you'll find that the Firebase configuration exists as a property in the environment for the initializeApp invocation.

If you're unsure where to find the Firebase configuration properties for your application, go the console, open your project and click the gear icon shown in the graphic below.  Scroll down on that page, and the configuration should appear under the SDK setup and configuration section.

Run the File Upload Application

If you were following along using the Nx Starter Monorepo, start the application by running the command below.

npx nx serve ng-serve-io-ng-serve

Go to the path http://localhost:4200/forms/file-upload-list.  You'll be able to add files, upload to Firebase, and remove files once completed.

Should you find a 403 error on upload, ensure the Rules allow read/write for your files.  By default, requests made to storage deny unauthorized requests.  For testing purposes, set the allow read, write: if true;.  Once you've completed testing, change the rule back to request.auth != null to prevent unauthorized file uploads.

Summary

You learned how to create an upload component with Angular Material that uploads to Firebase.  Many options exist for unstructured file storage, so an abstraction using the IFileUploadService interface allows different implementations based on requirements.