Angular 9 file downloaded corrupted - post

I'm using Angular 9 with HttpClient and File-Saver to download Files from server for now I'm trying to download Excel files
refindicateurService :
downloadIndicateurById(indicateurCriteria: IndicateurCriteria): Observable<Blob> {
console.log(indicateurCriteria);
return this.authHttp.post( this.settings.server.url + `/indicateur/reportingIndicateur`, indicateurCriteria , {responseType: 'blob'})
.pipe(map(res => res));
}
Download Function :
downloadFile(response: Response) {
console.log(response);
var blob = new Blob([this.getResponseBody(response)], { type: response.type });
this.removeBusy();
// let fileName: any = response.headers.get("etag");
importedSaveAs(blob, 'data.xslx');
}
Then I call it here :
exportIndicateurAsXls(id) {
let indicateurCriteria = new IndicateurCriteria();
indicateurCriteria.id = id;
this.addBusy();
this.refindicateurService.downloadIndicateurById(indicateurCriteria)
.subscribe(response => {
this.downloadFile(response);
this.removeBusy();
}, error => this.showError(error.status, JSON.parse(JSON.stringify(error)).message));
}
I get the file downloaded but corrupted when using 'arraybuffer' it gives undefined data
Fix :
downloadFile(response: Blob) {
this.removeBusy();
importedSaveAs(response, 'data.xslx');
}

Related

how to display a file using react-native

So, I have those "cards" to which are attached files.
I want to be able to display the content of these files (when possible; I do not expect to show binary files obviously, but text, pdf, images,...) to the user.
Upon a longPress on an attachment, the openAttachment() function is be called. That function downloads the file from the server if necessary and then (tries to) open it:
// Opens an attachment
const openAttachment = async (attachment) => {
try {
// Download file if not already done
const fileInfo = await FileSystem.getInfoAsync(FileSystem.cacheDirectory + attachment.name)
let uri
if (!fileInfo.exists) {
console.log('Downloading attachment')
resp = await FileSystem.downloadAsync(
server.value + `/index.php/apps/deck/api/v1.0/boards/${route.params.boardId}/stacks/${route.params.stackId}/cards/${route.params.cardId}/attachments/${attachment.id}`,
FileSystem.cacheDirectory + attachment.name,
{
headers: {
'Authorization': token.value
},
},
)
console.log(resp)
uri = await FileSystem.getContentUriAsync(resp.uri)
} else {
console.log('File already in cache')
uri = await FileSystem.getContentUriAsync(fileInfo.uri)
}
console.log('Opening file ' + uri)
Sharing.shareAsync(uri);
} catch {
Toast.show({
type: 'error',
text1: i18n.t('error'),
text2: error.message,
})
console.log(error)
}
}
The issue always arrise at the Sharing.shareAsync(uri); line: Whatever I put there, it fails:
Sharing.shareAsync(uri) does not seem to be supported on my platform: https://docs.expo.dev/versions/latest/sdk/sharing/
Linking.openURL(uri) does not support the file:// scheme (the uri is in the form file:///var/mobile/Containers/Data/Application/5C1CB402-5ED1-4E17-B907-46111AE3FB7C/Library/Caches/test.pdf)
await WebBrowser.openBrowserAsync(uri) (from expo-web-browser) does not seem to be able to open local files
How am I supposed to do to display those files? Anyone has an idea?
Cyrille
I found a solution using react-native-file-viewer
// Opens an attachment
const openAttachment = async (attachment) => {
try {
// Download file if not already done
const fileInfo = await FileSystem.getInfoAsync(FileSystem.cacheDirectory + "attachment.name")
let uri
if (!fileInfo.exists) {
console.log('Downloading attachment')
const resp = await FileSystem.downloadAsync(
server.value + `/index.php/apps/deck/api/v1.0/boards/${route.params.boardId}/stacks/${route.params.stackId}/cards/${route.params.cardId}/attachments/${attachment.id}`,
FileSystem.cacheDirectory + attachment.name,
{
headers: {
'Authorization': token.value
},
},
)
console.log(resp)
uri = await FileSystem.getContentUriAsync(resp.uri)
} else {
console.log('File already in cache')
uri = await FileSystem.getContentUriAsync(fileInfo.uri)
}
console.log('opening file', uri)
FileViewer.open(uri)
} catch(error) {
Toast.show({
type: 'error',
text1: i18n.t('error'),
text2: error.message,
})
console.log(error)
}
}

ios converting file to blob in CDVWKWebViewEngine ionic cordova angularJS

Just trying my luck here.
I've been tasked to update a Ionic 1, AngularJS, Cordova app for the changes in iOS camera and file permissions.
In most areas on the app I can simply convertFileSrc and trustAsResourceUrl to retrieve and display in the app. In the scenario below I need to convert the new safe ionic path to a blob for posting.
original file path format:
file:///var/mobile/Containers/Data/Application/10298A9F-7E24-48C4-912D-996EA731F2B0/Library/NoCloud/cdv_photo_001.jpg
after sanitising:
ionic://localhost/app_file/var/mobile/Containers/Data/Application/10298A9F-7E24-48C4-912D-996EA731F2B0/Library/NoCloud/cdv_photo_001.jpg
below is what I've tried so far:
function getDirectory(path) {
return path.substr(0, path.lastIndexOf('/'));
}
function getFilename(path) {
return path.substr(path.lastIndexOf('/') + 1);
}
function getImage(path) {
const pathStr = path;
if (window.ionic.Platform.isIOS() && typeof path === 'string') {
const fixedURL = window.Ionic.WebView.convertFileSrc(path);
path = $sce.trustAsResourceUrl(fixedURL);
}
if (typeof navigator.camera === 'undefined') {
return window.fetch(path, { method: 'GET' })
.then(function(response) {
return response.blob();
});
}
const filename = getFilename(`${path}`);
const directory = getDirectory(`${path}`);
return $cordovaFile.readAsArrayBuffer(directory, filename)
.catch(function(error) {
const err = {
message: 'read failed for image',
filename,
directory,
error
};
$log.error(`getImage, Error: ${JSON.stringify(err)}`);
return $q.reject(err);
})
.then(function(data) {
return new Blob([data], { type: 'image/jpeg' });
});
}
for anyone else who might need to do this in the future I figured out that it was the location of saved files casuing the problem.
In the cordova getPicture config I just needed to add
saveToPhotoAlbum: true
from there the file can just be pulled with the fetch api within the need for $cordovaFile:
return window.fetch(path, { method: 'GET' })
.then(function(response) {
return response.blob();
});

Download image not working on IOS device - React Native

I implemented functionality for download image in React Native using RNFetchBlob Its Working fine with android but on IOS device it's not working.
Following is my react native code for download functionality.
downloadImg = (url) => {
var that = this;
async function requestCameraPermission() {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
{
title: 'Test App',
message: 'Test App needs access to your gallery ',
}
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
that.actualDownload(url);
} else {
Alert.alert('Permission Denied!', 'You need to give storage permission to download the file');
}
}
if (Platform.OS === 'android') {
requestCameraPermission();
} else {
this.actualDownload(url);
}
}
actualDownload = (url) => {
var date = new Date();
var image_URL = url;
var ext = this.getExtention(image_URL);
ext = "." + ext[0];
const { config, fs } = RNFetchBlob;
let PictureDir = Platform.OS === 'ios' ? fs.dirs.DocumentDir : fs.dirs.PictureDir;
let options = {
fileCache: true,
addAndroidDownloads: {
useDownloadManager: true,
notification: true,
path: PictureDir + "/Images/image_" + Math.floor(date.getTime()
+ date.getSeconds() / 2) + ext,
description: 'Image'
}
}
config(options).fetch('GET', image_URL).then((res) => {
console.log("Thank you","Image Downloaded Successfully.");
});
}
the addAndroidDownloads only works for android. when you use the addAndroidDownloads the path in config is not useless. but for ios, the path has to be added.
try the following code, and you can add progress for ios.
actualDownload = (url) => {
var date = new Date();
var image_URL = url;
var ext = this.getExtention(image_URL);
ext = "." + ext[0];
const { config, fs } = RNFetchBlob;
let PictureDir = Platform.OS === 'ios' ? fs.dirs.DocumentDir : fs.dirs.PictureDir;
let options = {
fileCache: true,
path: PictureDir + "/Images/image_" + Math.floor(date.getTime() // add it
addAndroidDownloads: {
useDownloadManager: true,
notification: true,
path: PictureDir + "/Images/image_" + Math.floor(date.getTime()
+ date.getSeconds() / 2) + ext,
description: 'Image'
}
}
config(options).fetch('GET', image_URL).then((res) => {
console.log("Thank you","Image Downloaded Successfully.");
});
}
here are the document says:
When using DownloadManager, fileCache and path properties in config will not take effect, because Android DownloadManager can only store files to external storage, also notice that Download Manager can only support GET method, which means the request body will be ignored.
That method works for ios as well. The notification doesn't come. If you want to check that it is downloaded or not, you can check it by logging the path in terminal(Mac) or cmd(Windows) and access it through the terminal itself.
It might not be visible in finder(Mac) or file explorer(Windows).
You can follow the path that you log from the function in this way:
cd ~/Library/Developer/CoreSimulator/Devices...

Unable to generate excel file from XLSX in ionic

I am creating excel file from user data but unfortunately its not generating file and even don't know what is the error so I can at-least try to solve that error.
Specially this issue occur in iOS platform only.
Please find below code to generate excel file:
public createXSLX(): Promise<any> {
return new Promise((resolve) => {
let sheet = XLSX.utils.json_to_sheet(this.data);
let wb = {
SheetNames: ["export"],
Sheets: {
"export": sheet
}
};
let wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: false,
type: 'binary'
});
function s2ab(s) {
let buf = new ArrayBuffer(s.length);
let view = new Uint8Array(buf);
for (let i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
let blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' });
resolve(blob);
});
}
This above function works very well in android but in iOS its not generation file from provided data.
How I call above function code :
onExportNew = function (toEmail) {
this.createXSLX().then((xclBlob) => {
let time = new Date().getTime();
let fileName: string = "roster_" + time + ".xlsx";
var fs = ''
if (this.platform.is('ios')){
fs = this.file.documentsDirectory;
}else{
fs = this.file.externalDataDirectory;
}
console.log("File Path:- ",fs)
this.file.writeFile(fs, fileName, xclBlob, true).then(() => {
let fp = fs + fileName;
let email = {
// to: 'lmahajan#cisco.com',
// cc: 'erika#mustermann.de',
// bcc: ['john#doe.com', 'jane#doe.com'],
to: toEmail,
attachments: [fp],
subject: 'Roster Excel File',
body: '<h1>PFA</h1>',
isHtml: true
};
this.emailComposer.open(email).then(() => {
this.showDone = true;
}).catch(() => {
let toast = this.toastCtrl.create({
message: 'Could not open email composer',
duration: 3000
});
toast.present();
});
}).catch(() => {
this.displayAlert('Error', 'error creating file at: ' + fs);
});
}).catch(() => {
console.log("Excel file creation error");
});
}
Guide me if missing anything in above code.
Thanks in advance!

How to upload file in angular 2

This is the function I am using to upload file but is is giving me the error : Length is undefined. what I have to change in this code. where to give path of file to upload.
fileChange(event) {
let fileList: FileList = event.target.files;
if(fileList) {
let file: File = fileList[0];
let formData:FormData = new FormData();
formData.append('uploadFile', file, file.name);
let headers = new Headers();
/** No need to include Content-Type in Angular 4 */
headers.append('Content-Type', 'multipart/form-data');
headers.append('Accept', 'application/json');
let options = new RequestOptions({ headers: headers });
this.http.post(`assets/Files/info.txt`, formData, options)
.map(res => res.json())
.catch(error => Observable.throw(error))
.subscribe(
data => console.log(fileList),
error => console.log(error)
)
}
}
you need to use xhr request to transfer files
fileChange(event: EventTarget) {
let eventObj: MSInputMethodContext = <MSInputMethodContext> event;
let target: HTMLInputElement = <HTMLInputElement> eventObj.target;
let files: FileList = target.files;
if(files) {
let file: File = files[0];
this.upload(file)
}
}
public upload(filedata: File) {
let url = 'your url'
if (typeof filedata != 'undefined') {
return new Promise((resolve, reject) => {
let formData: any = new FormData();
let xhr = new XMLHttpRequest();
formData.append('icondata', filedata, filedata.name);
xhr.open('POST', url, true);
xhr.setRequestHeader('Authorization', 'JWT ' + localStorage.getItem('id_token'));
xhr.send(formData);
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
resolve(JSON.parse(xhr.responseText));
}
}
});
}
}
I understand that this is not the functionality you want to have but with no backend you can not upload files to be persistent, they should be stored somewhere. If you just wanna manipulate file names for instance, skip the express part in my answer. I personally used this code which I altered to upload multiple files.
In your Component :
import {FormArray, FormBuilder, FormControl, FormGroup} from "#angular/forms";
declare FormBuilder in the constructor:
constructor (private http: Http, private fb: FormBuilder) {}
in ngOnInit() set a variable as follows :
this.myForm = this.fb.group({chosenfiles: this.fb.array([])});
this is the code for the upload method :
// invoke the upload to server method
// TODO
// Should be in a service (injectable)
upload() {
const formData: any = new FormData();
const files: Array<File> = this.filesToUpload;
//console.log(files);
const chosenf = <FormArray> this.myForm.controls["chosenfiles"];
// iterate over the number of files
for(let i =0; i < files.length; i++){
formData.append("uploads[]", files[i], files[i]['name']);
// store file name in an array
chosenf.push(new FormControl(files[i]['name']));
}
this.http.post('http://localhost:3003/api/upload', formData)
.map(files => files.json())
.subscribe(files => console.log('upload completed, files are : ', files));
}
the method responsible for the file change :
fileChangeEvent(fileInput: any) {
this.filesToUpload = <Array<File>>fileInput.target.files;
const formData: any = new FormData();
const files: Array<File> = this.filesToUpload;
console.log(files);
const chosenf = <FormArray> this.myForm.controls["chosenfiles"];
// iterate over the number of files
for(let i =0; i < files.length; i++){
formData.append("uploads[]", files[i], files[i]['name']);
// store file name in an array
chosenf.push(new FormControl(files[i]['name']));
}
}
Template is something like this
<input id="cin" name="cin" type="file" (change)="fileChangeEvent($event)" placeholder="Upload ..." multiple/>
Notice multiple responsible for allowing multiple selections
The express API which will handle the request uses multer after an npm install
var multer = require('multer');
var path = require('path');
specify a static directory which will hold the files
// specify the folder
app.use(express.static(path.join(__dirname, 'uploads')));
As specified by multer
PS: I did not investigate multer, as soon as i got it working, i moved to another task but feel free to remove unnecessary code.
var storage = multer.diskStorage({
// destination
destination: function (req, file, cb) {
cb(null, './uploads/')
},
filename: function (req, file, cb) {
cb(null, file.originalname);
}
});
var upload = multer({ storage: storage });
And finally the endpoint
app.post("/api/upload", upload.array("uploads[]", 12), function (req, res) {
console.log('files', req.files);
res.send(req.files);
});

Resources