Downloading & opening a file in an Ionic app

• 6 min read

If you search the Internet for examples to achieve the above, you'll get a number of resources that suggest using the cordova-file-transfer plugin. However, with Ionic 3+ and its native API, you can do this directly using a mix of Angular and Ionic File API.

Essentially, you need to use Angular HttpClient to download a URL as a Blob using the [responseType] option and then save the resultant Blob to a file on the device using the File API. Finally, open the saved file using the Ionic native FileOpener API.

Here's the method in a provider that is used to download files from a website. Note that [this.http].

/**
  * Download a file from the given url.
  *
  * Params:
  *   url - the URL to download the file from
  * Returns:
  *   A promise which upon resolution returns a dictionary 
  *      {
  *        fileEntry: FileEntry,
  *        type: The content-type string for the file
  *      }
  */
  download(url): Promise<{ fileEntry: FileEntry, type: string }> {
    return new Promise((resolve, reject) => {

      // Get the file from the given URL argument. Note responseType is set to 'blob'.
      // It can also be set to 'arraybuffer', but response.body will be of different
      // type and needs to be handled slightly differently.
      this.http.get(url, {
        observe: 'response',
        responseType: 'blob'})
        .subscribe(response => {

          // Create /Download folder if it doesn't exist
          this.file.createDir(this.file.externalRootDirectory, 'Download', true)
            .then(de => {

              // Write the downloaded Blob as the file. Note that the file will
              // be overwritten if it already exists!
              this.file.writeFile(
                de.toURL(),
                fileName, response.body,
                { replace: true })
                .then(fe => {

                  // All went well, resolve the Promise.
                  return resolve({
                    fileEntry: fe,  // FileEntry instance
                    type: response.body.type  // Content-Type for the file
                  });

                }).catch(err => {

                  // writeFile failed 
                  reject(err);

                })
            }).catch(err => {

              // createDir failed 
              reject(err);

            })
        }, err => {

          // Download failed 
          reject(err);

        });
    });
  }

One of the key points here is that the [responseType: 'blob'] allows us to retrieve the file type as determined by the server which we can use as argument to the FileOpener API to open the file.

The argument [observe: 'response'] is not really necessary and can be left out. In this case, the subscribe handler will get the blob object as its argument instead of the higher level HTTP response object.

The call to [File.createDir()] ensures that the Download folder is created and available before the downloaded content is saved into it.

Finally the call to [File.writeFile()] saves the file and the function returns the FileEntry return value from this call.

This being a method in a service, it only downloads the file saving it to the device storage. How the saved file is to be processed is left to the page that initiated the download request to decide.

If the page wants to open the file, it can done like this:

this.fileOpener.open(attachment.fileEntry.nativeURL, attachment.type)
  .then(() => {
    // nothing to do actually
  }).catch(e => {
    // show alert message that file open failed.
  });

Important thing to note here is that we're supplying the [FileEntry.nativeURL] can be used as second argument to help the device determine the most appropriate application to open the file with.

If there are no applications associated with the given file type, [open() ]would result in an error, which should be caught and reflected to the user with an appropriate message.