Keywords: Angular 6 | File Download | REST API | Blob Handling | HttpClient
Abstract: This article provides a comprehensive analysis of common issues and solutions when downloading PDF files from REST APIs in Angular 6 applications. It covers key technical aspects including HttpClient response type configuration, Blob object handling, and browser compatibility, with complete code examples and best practices. The article also delves into server-side Spring Boot file return mechanisms to help developers fully understand file download implementation principles.
Problem Background and Error Analysis
When implementing file download functionality in Angular 6 applications, developers often encounter the <span style="font-family: monospace;">"Unexpected token % in JSON at position 0"</span> error. The root cause of this error is that HttpClient defaults to parsing responses as JSON format, while file downloads return binary data streams.
When the server returns a PDF file, the response body contains binary data, but Angular's HttpClient attempts to parse it as JSON, causing parsing failures. The <span style="font-family: monospace;">"%"</span> character in the error message is typically the beginning portion of the PDF file's binary data, confirming the improper response type configuration.
Server-Side Implementation Details
In the Spring Boot backend, the correct file download implementation should use <span style="font-family: monospace;">ResponseEntity<InputStreamResource></span>:
@GetMapping("/help/pdf2")
public ResponseEntity<InputStreamResource> getPdf2() {
Resource resource = new ClassPathResource("/pdf-sample.pdf");
long contentLength = 0;
InputStream inputStream = null;
try {
inputStream = resource.getInputStream();
contentLength = resource.contentLength();
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
return ResponseEntity.ok()
.contentLength(contentLength)
.contentType(MediaType.parseMediaType("application/pdf"))
.body(new InputStreamResource(inputStream));
}This implementation ensures:
- Correct Content-Type header set to <span style="font-family: monospace;">application/pdf</span>
- Accurate content length information
- Efficient streaming transmission to prevent memory overflow
Angular Service Layer Optimization
In the Angular service, the key configuration is properly setting <span style="font-family: monospace;">responseType: 'blob'</span>:
getPdf(): Observable<Blob> {
const httpOptions = {
responseType: 'blob' as 'json'
};
return this.http.get(`${this.BASE_URL}/help/pdf2`, httpOptions);
}Several important details to note here:
- The type assertion <span style="font-family: monospace;">responseType: 'blob' as 'json'</span> addresses TypeScript type checking issues
- No need to set Content-Type in headers for GET requests
- Add Authorization token in headers if API requires authentication
Component Layer Implementation and Browser Compatibility
When handling download responses in components, create Blob objects and trigger downloads:
downloadPdf(): void {
this.downloadService.getPdf().subscribe((data: Blob) => {
const blob = new Blob([data], { type: 'application/pdf' });
// Create download link
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = 'help.pdf';
// Trigger download
link.click();
// Clean up resources
window.URL.revokeObjectURL(downloadUrl);
});
}For better browser compatibility, consider this enhanced implementation:
downloadPdfEnhanced(fileName: string): void {
this.downloadService.getPdf().subscribe((data: Blob) => {
const blob = new Blob([data], { type: 'application/pdf' });
// IE/Edge special handling
if (window.navigator && (window.navigator as any).msSaveOrOpenBlob) {
(window.navigator as any).msSaveOrOpenBlob(blob, fileName);
return;
}
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName;
// Firefox compatibility handling
link.dispatchEvent(new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
}));
// Delayed resource cleanup
setTimeout(() => {
window.URL.revokeObjectURL(downloadUrl);
link.remove();
}, 100);
});
}Error Handling and Best Practices
In practical applications, comprehensive error handling should be added:
downloadPdfWithErrorHandling(): void {
this.downloadService.getPdf().subscribe({
next: (data: Blob) => {
try {
const blob = new Blob([data], { type: 'application/pdf' });
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = 'help.pdf';
link.click();
window.URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error('Download processing error:', error);
// Display user-friendly error messages
}
},
error: (error) => {
console.error('API request error:', error);
// Handle network errors, authentication failures, etc.
}
});
}Performance Optimization Considerations
For large file downloads, consider the following optimization strategies:
- Use chunked transfer
- Implement download progress display
- Add file size validation
- Consider using Web Workers for large file processing
Security Considerations
When implementing file download functionality, pay attention to the following security aspects:
- Validate user permissions
- Prevent path traversal attacks
- Set appropriate CORS policies
- Perform security scanning on file content
Through this complete implementation solution, developers can avoid common file download errors and provide stable and reliable file download functionality.