Auto Bootstrapping is the process of automatically initiating the DOMContentLoaded event in the browser. The AngularJS application after downloading the angular.js library into the browser does the task of finding the ng-app directive which gives the root of the application. Once the directive is found, the following steps take place:
File downloading is a core aspect of surfing the internet. Tons of files get downloaded from the internet every day ranging from binary files (like applications, images, videos, and audios) to files in plain text.
A simple download link can be easily achieved with plain HTML in Angular. You'll use an anchor tag pointing to the file with the href attribute. The download attribute informs the browser that it shouldn't follow the link but rather download the URL target. You can also specify its value in order to set the name of the file being downloaded.
Older browsers, like the Internet Explorer, might not recognize the download attribute. For those cases you can open the download in a new browser tab with the target attribute set to _blank. Make sure though to always include rel="noopener noreferrer" when you're using target="_blank" so you're not opening yourself up to security vulnerabilities.
If there's no download attribute, the filename for your download will solely depend on the HTTP header Content-Disposition sent by the server that's providing the file. The information from this header might also take precedence even if the download attribute is present.
A link-based solution conforms well to HTML standards and lets the browser do most of the work. However, if you want more control over the download and would like to display some custom progress indicator you can also download files via Angular's HttpClient.
Here, we're creating an anchor tag programmatically when the blob arrives. With URL.createObjectURL we can generate a download link to the blob. Finally, we click() the link like the user would've done with a regular browser download link. After the file is downloaded, we'll discard the blob by revoking the object URL we created.
Since we don't just want to forward these events to every component, our service has to do some more work. Otherwise our component would have to deal with HTTP specifics - that's what services are for! Instead let's introduce a data structure representing a download with progress:
A Download can be in one of three states. Either it hasn't started yet, therefore it's pending. Otherwise it's done or still in progress. We use TypeScript's union types to define the different download states. Additionally, a download has a number indicating the download progress from 1 to 100. Once a download is done, it will contain a Blob as its content - until then this property is not available, therefore null.
Based on these guards we can now create our custom operator. It'll leverage scan, an operator that allows us to accumulate state for successive values coming through an observable. It takes up to two arguments: First, we provide an accumulator function which will compute the next Download state from the previous one and the current HttpEvent. Second, we'll pass a seed to scan representing the initial Download state. This seed will represent our download being pending without any progress or content:
When we encounter a HttpProgressEvent, we calculate the progress based on the number of bytes already loaded and the total bytes. A download is done when we receive a HttpResponse containing the file contents in its body. When receiving any other events than HttpProgressEvent or HttpResponse, we won't alter the download's state and return it as it is. This way, for example, we can keep the information in the progress property while other events that won't allow us to compute the progress can be ignored for now.
Notice that this download operator accepts an optional parameter saver. Once a HTTP response is received, this function is invoked with the download content from inside the accumulator. This allows us to pass in a strategy for persisting the download to a file without directly coupling the operator to FileSaver.js.
By keeping FileSaver.js out of our custom operator, the resulting code is more maintainable. The download operator can be tested without somehow mocking the saveAs import (see here for corresponding tests). If we apply the same pattern to the service, we'll be able to test it just as easy. So let's do that by creating a custom injection token for saveAs in a file called saver.provider.ts:
Let's use the Progress Bar from Angular Material to show how far along our download is. We'll create a component property for binding a download called download$. The component now only has to assign an observable download to this property:
We can then subscribe to this observable through the AsyncPipe in combination with NgIf. While the download is pending we'll display the progress bar in 'buffer' mode (you may also use 'query'), otherwise the progress is determinate. The bar's value can then easily be applied from Download.
It is also possible to protect images from downloading by right-click. While this does not protect from truly determined users, it should discourage the vast majority from ripping off your files. Optionally, put the watermark over image.