An angular download directive
The angular way of downloading files is a little confusing, you need to create an anchor and add the data you want to download to it, it looks like so:
downloadFile(data: Blob, fileName: string) { const a = document.createElement('a'); const objectUrl = URL.createObjectURL(data); a.href = objectUrl; a.download = fileName; a.click(); URL.revokeObjectURL(objectUrl); }
Instead of repeating this code everywhere I wannted to see if I could add it to a directive.
For this create a new directive like so:
@Directive({ selector: '[onDownloadFile]' }) export class DownloadFileDirective { @Input() downloadFunc: () => Observable<string[]> = () => of(); @Input() contentType: string = ''; @Input() fileName: string = ''; @HostListener('click', ['$event']) onClick() { this.downloadFunc().subscribe((value: string[]) => this.downloadFile( new Blob(value, {type: this.contentType}), this.fileName )); } downloadFile(data: Blob, fileName: string) { const a = document.createElement('a'); const objectUrl = URL.createObjectURL(data); a.href = objectUrl; a.download = fileName; a.click(); URL.revokeObjectURL(objectUrl); } }
It's a lot of code but most is generated automatically by the command
The directive is called onDownloadFile and will handle the onClick event. To make it generic it accepts as input:
a callback function that does the async call to the backend or wathever
a content type for the popups of the browser
the final file name of the download
In this example the backend returns a string[] but the blob coud directly been returned instead.
You do not want to trigger the downloadFunc unnecessarily, that's why we pass a function and not the function call as first input of our directive:
@Input() downloadFunc: () => Observable<string[]> = () => of();
The notation is a little cryptic, also inputs need to be initialized so there is that.
Here we define downloadFunc as a function that returns an observable downloadFunc: () => Observable<string[]> and declare an initial version returning an empty observable = () => of()
Using this new directive is quite straightforward except for a little caveat. The function passed needs to bind the scope, otherwise our method downloadCsr will not work:
<button mat-flat-button class="download-btn" onDownloadFile [downloadFunc]="downloadCsr.bind(this)" [contentType]="'application/pkcs10'" [fileName]="cert.commonName + '.pem'"> {{'certificate.detail.downloadCSR' | translate}} </button>
Add onDownloadFile to tell that it should use the directives
Pass the download function as paramter. Here we bind the scope so that the callback works with this object.
contentType and fileName are passed to customize the download
Now you can re-use this accross your app for all your file download needs.
The source code is available in a convenient gist