How dont create stream each time after change input? - javascript

I have the following code:
public upload(): void {
const fileinput = document.getElementById('file');
fileinput.click();
fileinput.addEventListener('change', this.handleFileInput.bind(this));
}
private handleFileInput(event) {
try {
const input = event.target as HTMLInputElement;
const reader = new FileReader();
const { objectId } = this.object;
const file = input.files[0];
reader.onload = () => {
event.target.value = null;
this.objectDetailsService
.upload(file, objectId)
.subscribe(
() => //,
() => //,
);
};
reader.readAsArrayBuffer(file);
} catch (e) {
console.log(e);
}
}
I should allow to load the same file sometimes. When user select another this code create as new stream. How to avoid it?

You're right in asking how to apply switchMap. One way is to create an observable to handle the async callback onload. Then you could apply operators like switchMap to call other async functions.
Also I'd suggest attaching the event handler directly in the template instead of using document.getElementById('file'); in the controller.
Try the following
*.ts
import { Observable, Observer, timer } from 'rxjs';
import { switchMap } from 'rxjs/operators';
public handleFileInput(event) {
const input = event.target as HTMLInputElement;
const file = input.files[0];
this.readFile(file).pipe(
switchMap((file: any) => {
const { objectId } = this.object;
return this.objectDetailsService.upload(file, objectId);
})
).subscribe({
next: () => console.log('File uploaded'),
error: (error: any) => console.log('Error:', error),
});
}
readFile(file): Observable<any> {
const reader = new FileReader();
reader.readAsArrayBuffer(file);
return new Observable((observer: Observer<any>) => {
reader.onload = (ev: ProgressEvent) => {
observer.next(file);
observer.complete();
};
reader.onerror = (error: any) => {
observer.error(error);
};
});
}
*.html
Select file: <input type="file" (change)="handleFileInput($event)" />
Working example: Stackblitz

Related

Preventing duplicate files in an input file

I'm using loodash cloneDeep to upload files, however I need files to not be duplicated and only be able to upload a file once. How can I do this using cloneDeep?
I don't know how to do it, I googled, but the solution was only for jquery
const [files, setFiles] = useState([]);
const onSelectFile = (e) => {
try {
let fileArr = cloneDeep(files);
let promises = [];
for (let file of e.target.files) {
promises.push(
new Promise((resolve, reject) => {
const fileName = file.name
const type = file.type;
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (evt) {
const fileData = evt.target.result;
fileArr.push({
name: fileName,
type: type,
data: fileData,
comment: "",
id: `${new Date().getTime()}_${fileName}`,
canDelete: true
});
if (typeof props.onFileSelected == "function")
props.onFileSelected(fileArr);
resolve(true);
}
reader.onerror = function (evt) {
console.log("error reading file");
reject(false);
}
})
);
}
Promise.all(promises).then(r => {
setFiles(fileArr);
})
}
catch(e) {
console.log(e);
}
}
If relying on the filenames is enough, you can try to store them to check if it has been uploaded already :
const [files, setFiles] = useState([]);
//state to store uploaded file's name
const [fileNames, setFileNames] = useState([]);
const onSelectFile = (e) => {
try {
let fileArr = cloneDeep(files);
let promises = [];
for (let file of e.target.files) {
promises.push(
new Promise((resolve, reject) => {
const fileName = file.name
//if the file has not been already uploaded
if (!fileNames.includes(fileName)) {
//add the current fileName in state
setFileNames([fileName, ...fileNames]);
const type = file.type;
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (evt) {
const fileData = evt.target.result;
fileArr.push({
name: fileName,
type: type,
data: fileData,
comment: "",
id: `${new Date().getTime()}_${fileName}`,
canDelete: true
});
if (typeof props.onFileSelected == "function")
props.onFileSelected(fileArr);
resolve(true);
}
reader.onerror = function (evt) {
console.log("error reading file");
reject(false);
}
} else {
alert("File has already been uploaded");
reject(false);
}
})
);
}
Promise.all(promises).then(r => {
setFiles(fileArr);
})
}
catch(e) {
console.log(e);
}
}
Note: this will not prevent the case when the user upload a file, then refresh the website and upload the same file again
If you want to prevent that you have to ask your backend if the file has already been upload or not.

Removing duplicate files upload-files

I use lodash clonedeep for uploading files.
I wrote a function that forbids uploading identical files. But if I delete some file after uploading, it still stays in state and I can't upload file with the same name.
What can I do to get the file removed from the state too?
const [files, setFiles] = useState([]);
//state to store uploaded file's name
const [fileNames, setFileNames] = useState([]);
const onSelectFile = (e) => {
try {
let fileArr = cloneDeep(files);
let promises = [];
for (let file of e.target.files) {
promises.push(
new Promise((resolve, reject) => {
const fileName = file.name
//if the file has not been already uploaded
if (!fileNames.includes(fileName)) {
//add the current fileName in state
setFileNames([fileName, ...fileNames]);
const type = file.type;
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (evt) {
const fileData = evt.target.result;
fileArr.push({
name: fileName,
type: type,
data: fileData,
comment: "",
id: `${new Date().getTime()}_${fileName}`,
canDelete: true
});
if (typeof props.onFileSelected == "function")
props.onFileSelected(fileArr);
resolve(true);
}
reader.onerror = function (evt) {
console.log("error reading file");
reject(false);
}
} else {
alert("File has already been uploaded");
reject(false);
}
})
);
}
Promise.all(promises).then(r => {
setFiles(fileArr);
})
}
catch(e) {
console.log(e);
}
}
I don’t know what to do, it took me 40 hours to think, but I still didn’t understand anything.
From what I can understand from your question, you aren't saving the images on disk. You also haven't included the logic you are using for deleting files. Either way, the implementation is similar.
So when a user deletes a file, assuming they are deleting by filename, we use the filter() method to only keep those that aren't the file we want to delete.
const fileNameToRemove = 'example.txt';
setFiles(files.filter(file=> file.name !== fileNameToRemove));
setFileNames(fileNames.filter(name => name !== fileNameToRemove));
So you will want to do something like this, I haven't used clonedeep like you're in this example, but it's a quick add. I have also moved the read file section into its own function, and am I returning a promise, so I can use async/await within the core upload function.
const [files, setFiles] = useState([]);
const [fileNames, setFileNames] = useState([]);
const readFileAsync = async (file) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.onload = () => {
resolve(fileReader.result);
}
fileReader.onerror = () => {
reject(null);
}
})
}
const onSelectFile = async (e) => {
const uploadingFiles = e.target.files;
for (const file of uploadingFiles) {
const fileName = file.name;
if (fileNames.includes(fileName)) {
console.error("File duplicate");
continue;
}
try {
const fileContentsBuffer = await readFileAsync(file);
setFiles([...files, {
name: fileName,
type: file.type,
data: fileContentsBuffer,
comment: "",
id: `${new Date().getTime()}_${fileName}`,
canDelete: true
}])
setFileNames([...fileNames, fileName]);
} catch (e) {
console.error("Error reading file");
continue;
}
}
}
const deleteFile = (fileName) => {
const fileDataToDelete = files.find((file) => {
return file.name === fileName;
})
if (!fileDataToDelete.canDelete) {
console.error('Can\'t delete file!');
return;
}
setFileNames(fileNames.filter(name => name != fileName));
setFiles(files.filter(file => file.name != fileName && file.canDelete));
}

Wait for function to finish reading excel file

I want to read an excel file and return the data in json format. When I call 'readExcelSheet' function on button click, it returns 'undefined' as function hasn't been finished reading the excel data. On subsequent click, data does return properly. I want to wait for this function until it read complete data.
constructor(private httpClient: HttpClient) {
}
readExcelSheet() {
let dataJson;
this.httpClient.get(this.filePath, { responseType: 'blob' })
.subscribe((data: any) => {
const reader: FileReader = new FileReader();
reader.onload = (e: any) => {
// reader.readAsBinaryString(e.target.files[0]);
const bstr: string = e.target.result;
const wb: XLSX.WorkBook = XLSX.read(bstr, { type: 'binary' });
/* grab first sheet */
const wsname1: string = wb.SheetNames[0];
const ws1: XLSX.WorkSheet = wb.Sheets[wsname1];
dataJson = XLSX.utils.sheet_to_json(ws1);
};
reader.readAsBinaryString(data);
});
return dataJson;
}
When you call subscribe, the code executes this line asynchronously (ie: skips over this line and go to the return immediately). Use lastValueFrom in an async-await function instead.
Try doing this:
async readExcelSheet(){
let dataJson;
const data = await lastValueFrom(this.httpClient.get(this.filePath, { responseType: 'blob' }))
const reader: FileReader = new FileReader();
reader.onload = (e: any) => {
const bstr: string = e.target.result;
const wb: XLSX.WorkBook = XLSX.read(bstr, { type: 'binary' });
/* grab first sheet */
const wsname1: string = wb.SheetNames[0];
const ws1: XLSX.WorkSheet = wb.Sheets[wsname1];
dataJson = XLSX.utils.sheet_to_json(ws1);
};
reader.readAsBinaryString(data);
return dataJson;
}
Try using ASYNC & AWAIT in your functions
async readExcelSheet()
Try to use toPromise instead of subscribe and then you can return the function.
You can then try to use Promise.resolve() to resolve the dataJson.
constructor(private httpClient: HttpClient) {
}
readExcelSheet() {
return this.httpClient.get(this.filePath, { responseType: 'blob' })
.toPromise().then((data: any) => {
const reader: FileReader = new FileReader();
reader.onload = (e: any) => {
// reader.readAsBinaryString(e.target.files[0]);
const bstr: string = e.target.result;
const wb: XLSX.WorkBook = XLSX.read(bstr, { type: 'binary' });
/* grab first sheet */
const wsname1: string = wb.SheetNames[0];
const ws1: XLSX.WorkSheet = wb.Sheets[wsname1];
Promise.resolve(XLSX.utils.sheet_to_json(ws1))
};
reader.readAsBinaryString(data);
});
}

REACT - auto submit form after files uploaded to state the contains array

I want to submit a form that send a request to server automatically.
This is how I load the files into the state -
const onUpload = (event: ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const id = event.target.id;
const fileReader = new FileReader();
const file = event.target.files![0];
fileReader.onload = () => {
setCSVFilesState([...CSVFilesState, { id, file: fileReader.result }]);
};
fileReader.readAsDataURL(file);
};
I'm uploading 2 files to the CSVFilesState both in the form.
I want to fire the request when both files are in the state. How can I wait for the files and only then send the request?
This is my onSubmit function -
const onSubmit = (event: React.FormEvent) => {
event.preventDefault();
backendAPIAxios
.post("/", CSVFilesState)
.then((response: AxiosResponse<IServerResponseData>) => {
})
.catch((e: AxiosError) => {
});
};
Tried to do that (didn't work) -
const onUpload = (event: ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const id = event.target.id;
const fileReader = new FileReader();
const file = event.target.files![0];
fileReader.onload = () => {
setCSVFilesState([...CSVFilesState, { id, file: fileReader.result }]);
};
fileReader.readAsDataURL(file);
if (CSVFilesState.length === 1) onSubmit(event);
};
useEffect(()=>{
if(CSVFilesState.length === 2){
onSubmit()
}
},[CSVFilesState])
Checked the count of CSVFilesState.

Angular 2 Synchronous File Upload

I am trying to upload a file to web api which takes the file as byte array using angular 2 application.
I am not able to pass the byte array from angular 2 page to web api. It looks like the File Reader read method is asynchronous. How do I make this as synchronous call or wait for the file content to be loaded before executing the next line of code?
Below is my code
//attachment on browse - when the browse button is clicked
//It only assign the file to a local variable (attachment)
fileChange = (event) => {
var files = event.target.files;
if (files.length > 0) {
this.attachment = files[0];
}
}
//when the submit button is clicked
onSubmit = () => {
//Read the content of the file and store it in local variable (fileData)
let fr = new FileReader();
let data = new Blob([this.attachment]);
fr.readAsArrayBuffer(data);
fr.onloadend = () => {
this.fileData = fr.result; //Note : This always "undefined"
};
//build the attachment object which will be sent to Web API
let attachment: Attachment = {
AttachmentId: '0',
FileName: this.form.controls["attachmentName"].value,
FileData: this.fileData
}
//build the purchase order object
let order: UpdatePurchaseOrder = {
SendEmail: true,
PurchaseOrderNumber: this.form.controls["purchaseOrderNumber"].value,
Attachment: attachment
}
//call the web api and pass the purchaseorder object
this.updatePoService
.updatePurchaseOrder(this.form.controls["purchaseOrderRequestId"].value, order)
.subscribe(data => {
if (data) {
this.saveSuccess = true;
}
else {
this.saveSuccess = false;
}
},
error => this.errors = error,
() => this.res = 'Completed'
);
}
Any hint would be useful.
regards,
-Alan-
You cannot make this async call synchronous. But you can take advantage of the observables to wait for the files to be read:
//when the submit button is clicked
onSubmit = () => {
let file = Observable.create((observer) => {
let fr = new FileReader();
let data = new Blob([this.attachment]);
fr.readAsArrayBuffer(data);
fr.onloadend = () => {
observer.next(fr.result);
observer.complete()
};
fr.onerror = (err) => {
observer.error(err)
}
fr.onabort = () => {
observer.error("aborted")
}
});
file.map((fileData) => {
//build the attachment object which will be sent to Web API
let attachment: Attachment = {
AttachmentId: '0',
FileName: this.form.controls["attachmentName"].value,
FileData: fileData
}
//build the purchase order object
let order: UpdatePurchaseOrder = {
SendEmail: true,
PurchaseOrderNumber: this.form.controls["purchaseOrderNumber"].value,
Attachment: attachment
}
return order;
})
.switchMap(order => this.updatePoService.updatePurchaseOrder(this.form.controls["purchaseOrderRequestId"].value, order))
.subscribe(data => {
if (data) {
this.saveSuccess = true;
} else {
this.saveSuccess = false;
}
},
error => this.errors = error,
() => this.res = 'Completed'
);
}
I arrived here looking for a solution for a similar issue. I'm performing requests to an endpoint which can response a binary blob if anything goes well or a JSON file in event of error.
this.httpClient.post(urlService, bodyRequest,
{responseType: 'blob', headers: headers})
.pipe(map((response: Response) => response),
catchError((err: Error | HttpErrorResponse) => {
if (err instanceof HttpErrorResponse) {
// here, err.error is a BLOB containing a JSON String with the error message
} else {
return throwError(ErrorDataService.overLoadError(err, message));
}
}));
As FileReaderSync apparently doesn't work in Angular6 I took n00dl3's solution (above) to throw the error after parsing the Blob content:
return this.httpClient.post(urlService, bodyRequest,
{responseType: 'blob', headers: headers})
.pipe(map((response: Response) => response),
catchError((err: Error | HttpErrorResponse) => {
const message = `In TtsService.getTts(${locale},${outputFormat}). ${err.message}`;
if (err instanceof HttpErrorResponse) {
const $errBlobReader: Observable<HttpErrorResponse> = Observable.create((observer) => {
const fr = new FileReader();
const errorBlob = err.error;
fr.readAsText(errorBlob, 'utf8');
fr.onloadend = () => {
const errMsg = JSON.parse(fr.result).message;
const msg = `In TtsService.getTts(${locale},${outputFormat}). ${errMsg}`;
observer.error(ErrorDataService.overLoadError(err, msg));
};
fr.onerror = (blobReadError) => {
observer.error(blobReadError);
};
fr.onabort = () => {
observer.error('aborted');
};
});
return $errBlobReader;
} else {
return throwError(ErrorDataService.overLoadError(err, message));
}
}));
Thanks! You really saved my day!

Categories

Resources