Angular 5: Multiple requests at once with forkJoin - javascript

Importand node
Was a problem within the back end, not angular, requests are correct.
I like to upload different files at once in my angular5 app. To do this I use rsjx forkJoin. I append the request in an array as you can see in the following code.
After adding the request to the array, they are all the same requests, meaning that I upload one file multiple times instead of the different selected files. Before I add them to the array, you can see in the screenshot, it's fine, different request are created by the service. I know this is some kind of pointer problem, but I have no idea how to fix this, I tried to make a deep copy with Object.assign({}, request), but doesn't work for the Observable.
Console log ~ ~ (ignore the error, the server refuses always the second request because of some unique constraint requirement - it is correct): ~ ~
The component code:
uploadFiles() {
var requests = [];
var ctrNoFailuer = this.filesReplace.length;
for (var i = 0; i < this.filesReplace.length; i++) {
let request = this.solutionFileService.update(this.filesReplace[i][0], this.filesReplace[i][1]);
console.log(request);
requests.push(request);
}
if (requests.length == 0) {
this.runTests();
} else {
forkJoin(requests).subscribe(
res => {
// [...]
}
)
}
}
The service looks like this:
update(solutionFile: SolutionFile, file?: File): Observable<SolutionFile> {
console.log(solutionFile);
let url = `${this.url}/src_uploads/${solutionFile.id}/`;
if (file) {
let headers = new HttpHeaders({
'enctype': 'multipart/form-data',
'Authorization': `JWT ${this.authService.getToken()}`
})
const formData: FormData = new FormData();
formData.append('student_solution', String(solutionFile.student_solution));
formData.append('file_src', file, file.name);
return this.httpClient.put<SolutionFile>(url, formData, { headers: headers })
.pipe(
catchError(this.handleError('UPDATE solution-file', new SolutionFile()))
);
}
return this.httpClient.put<SolutionFile>(url, solutionFile, { headers: this.headers })
.pipe(
catchError(this.handleError('UPDATE solution-file', new SolutionFile())
)
);
}

Your code seems correct, although you can make some improvements (factorisation, syntax ...), such as this :
uploadFiles() {
const requests = this.filesReplace.map(fr => this.solutionFileService.update(fr[0], fr[1]));
if (!requests.length) {
this.runTests();
} else {
forkJoin(requests).subscribe(
res => {
// [...]
}
)
}
}
update(solutionFile: SolutionFile, file ?: File): Observable < SolutionFile > {
let url = `${this.url}/src_uploads/${solutionFile.id}/`;
const headers = file ?
new HttpHeaders({
'enctype': 'multipart/form-data',
'Authorization': `JWT ${this.authService.getToken()}`
}) : this.headers;
const formData: FormData = new FormData();
formData.append('student_solution', String(solutionFile.student_solution));
formData.append('file_src', file, file && file.name || undefined);
const payload = file ? formData : solutionFile;
return this.httpClient.put<SolutionFile>(url, payload, { headers })
.pipe(
catchError(() => this.handleError('UPDATE solution-file', new SolutionFile()))
);
}
Also, please post your logs as text, because not everyone has access to imgur. Did you try to log the result of the forkJoin call ?

I haven't fully understood the question, but as I understood the forkJoin is not working for you, because you trying to pass array of observables. To overcome that issue, try to apply array of observables:
forkJoin.apply(this, requests).subscribe(
Hope this helps

Related

How to create javascript Request Object with Body type as form-data file

I am trying to code POST REST API request as shown in the above Postman screenshot.
Here is my code:
import { fromFetch } from 'rxjs/fetch';
import { switchMap } from 'rxjs/operators';
interface FetchRequest<TBody> {
method?: FetchMethod;
body?: TBody;
headers?: FetchHeaders;
isBlobRequest?: boolean;
}
export const request = <TBody extends BodyInit = BodyInit>(
url: string,
fetchRequest?: FetchRequest<TBody>,
contentType: string
) => {
const { method = 'POST', body, headers } = fetchRequest ?? {};
const { dispatch } = createStore();
let request = new Request(`${domainUrl}${url}`, {
method,
body: body ? body : null,
headers: {
...headers,
'Content-type': contentType ,
},
credentials: 'include',
});
return fromFetch(request).pipe(
switchMap((response: Response) => {
if (response.status === SUCCESS_CODE) {
if (isBlobRequest) {
return response.blob();
}
return response.text();
} else if (response.status === USER_SESSION_EXPIRED_CODE) {
dispatch(authAsyncActions.setSessionExpired());
throw response;
} else {
// This triggers error-handling and allows us to inspect the entire
// error response including its status and body
throw response;
}
})
);
};
const callUploadAPI = (file) => {
let formData = new FormData();
formData.append('file', file);
request(`urlUploadFile`, { method: 'POST', body: formData}, 'application/vnd.ms-excel')
}
In above code I am using fromFetch of "rxjs/fetch" to call the POST REST API and passing "Request" object in fromFetch().
Request interface is inside typescript as per the below screenshot.
Backend is Python flask server and on the backend side in Python code I am using file = request.files['file'] to get the file which is working when I call the API through Postman but when I call the API through frontend code it is not getting file.
How can I set the Request "Body" type as "form-data" and "KEY" type as File in frontend code?
You're explicitly setting the Content-type to application/vnd.ms-excel, but you don't need to set this header
As far as I know, if the body of a fetch request is a FormData object, the browser automatically sets the content type header to multipart/form-data

Zapier action to save file from API

I'm trying to create an action in zapier that will fetch raw bytes from my api and use it in zapier zaps after that.I actually stuck now, because none of what i've read in docs is working for me. Please any advice.
const options = {
url: `my test api call`,
method: 'GET',
raw: true,
headers: {
'Authorization': `Bearer ${bundle.authData.access_token}`
}
}
const stashPDFfunction = (z, bundle) => {
// use standard auth to request the file
const filePromise = z.request(bundle.inputData.options);
// and swap it for a stashed URL
return z.stashFile(filePromise);
};
var result = z.dehydrateFile(stashPDFfunction, {
options: options
});
return result;

How to send data correct axios Error: Multipart: Boundary not found

I don't know why I receive on server [Error: Multipart: Boundary not found]
and bundle.js:37628 POST http://localhost:8800/exporttocsv 500 (Internal Server Error)
When I make post through
<form action="/exporttocsv" method="POST" encType="multipart/form-data">
post works correctly, but through axios doesn't work.
Please help me fix the mistake
this my code
/--client
import axios from 'axios'
var formData = new FormData()
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
export const ipmortToCSV = (file) => dispatch => {
formData.append('file',file)
console.log(formData.getAll('data'))
axios.post('/exporttocsv', {
"UploadCommand": formData
},config)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
//--server
const router = require('express').Router()
var csv = require('csv-express')
const controllers = require('../../controllers/exporttocsv')
var multer = require('multer')
var upload = multer({dest : 'exporttocsv/'})
router.get('/', (req, res) => {
controllers.exportToCsv(req,res)
})
router.post('/',upload.single('file'),(req,res) => {
//controllers.importToCsv(req,res)
})
module.exports = router
You can do this ...
Instantiate a new FormData instance.
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
let fd = new FormData();
fd.append('file',files[0])
return axios.post("http://localhost:5000/upload", fd, config)
Usingconcat and concat-stream
const concat = require("concat-stream")
const fd = new FormData()
fd.append("hello", "world")
fd.append("file", fs.createReadStream(file))
fd.pipe(concat(data => {
axios.post("/hello", data, {
headers: fd.getHeaders()
})
}))
Using promise
const promise = new Promise((resolve) => {
const fd = new FormData();
fd.append("hello", "world");
fd.append("file", fs.createReadStream(binaryFile));
fd.pipe(concat({ encoding: 'buffer' }, data => resolve({ data, headers: fd.getHeaders() })));
});
promise.then(({ data, headers }) => axios.post('/hello', data, { headers }));
I hope I've been useful! :)
References:
github.com - Can't get a .post with Content-Type...
github.com - Better solution using axios, form-data, fs
https://stackoverflow.com/a/47630754/3332734
I was struggling with this issue of multipart boundary not found with fetch api calling to a nestjs server. What I tried was to remove the
'Content-Type': 'multipart/form-data',
headers so that Fetch api automatically set the headers and it worked. Try it out
By default axios do not attach boundary to content type header. You have to do it manually:
axios.post(`${this.baseUrl}/${path}`, formData, {
headers: {
'Content-Type': `multipart/form-data; boundary=${formData.getBoundary()}`,
},
})
It is especially important if you talking to spring server.
In other case you will see exception:
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
I was getting this problem with Axios via JavaScript because the content-type header was multipart-form-data but the boundary was missing.
Based on my research, a good way to handle it is to allow Axios to auto-detect the content type and set the headers correctly itself.
Here is an idea for how to accomplish this:
const formDataWithFiles = hasFiles ? new FormData() : undefined;
if (formDataWithFiles) {
// axios will automatically set the content-type to multipart/form-data if the
// data param is a FormData object
// otherwise, it will use application/json
// (study the Dev Tools > Network tab > XHR tab headers)
Object.keys(modifiedFields)
.forEach(field => formDataWithFiles.append(field, modifiedFields[field]));
}
const { data } = await axios({
method,
url: actionUrl,
data: hasFiles ? formDataWithFiles : modifiedFields,
headers: {
...axios.defaults.headers,
...headers,
},
});
return data;
The above code is in a generic handleSubmit function that can be called from anywhere in the client-side.
Here is the function signature:
const { data } = await this.submitForm({
actionUrl: this.actionUrl,
method: this.method,
modifiedFields: {
...this.modifiedUser,
},
hasFiles: true,
});
In the above code, there are two use cases. The first is the default case, where a normal payload is sent via a flat object. The second is the case when the form has files and you want multipart/form-data. In this case, we use the FormData Object as a vessel to instruct Axios to auto-detect the necessary headers and set the correct boundary.
If you do not specify the headers correctly, it is possible to receive an empty $request->all() Array in Laravel, or perhaps any server such as node.js.
The short answer to my answer is to use the FormData Object because it contains more information than a plain-old-JavaScript-object. With it, you can also access:
const formData = new FormData();
console.log('boundary:', formData._boundary);
As my annotation above hints towards, use the Dev Tools > Network tab > XHR tab to examine your request headers and make sure you have content-type application/json or application/x-www-form-urlencoded for regular form submits and multipart/form-data' if you are uploading a file.
For me the main reason was what the OP did; sending the data argument of axios.post as an object ({ key: formDataObj}) instead of just formDataObj directly as the arg.
For me add the following code to fixes it.
axios.post('/xxx/Upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
transformRequest: (data) => {
return data
},
})
Okay, I would like to share my solution as I struggled with this problem for almost a day. The issue was caused by an incorrect package version. Around 27.0.0, some changes were made to the form data sending, which resulted in issues with the boundaries. I'm not sure what version you folks are using, but it might be worth checking if this is the cause of your problem.
https://github.com/axios/axios/issues/4631

Updating part of data using patch or put methods

I want to update part of data through an API. I've successfully made a POST data. I would also like to do PATCH to update one field of the data.
Below is the code am trying to use for the changes:
else if (out_timing.worker_timeout_id === emp_id && out_timing.check_status === false) {
let checkOutTime = new TimeOutModel(out_timing.date, emp_id, out_timing.time_out, true);
//introduced two lines below to carry out update for last field
let updateCheckStatus = new TimeInModel("", "", "", false);
this.employeesService.patchTimeinStatus(updateCheckStatus)
this.employeesService.pushTimeOut(checkOutTime)
.subscribe(checkOutTime => {
},
error => this.errorMessage = <any>error);
console.log("Successfully checked-out!");
break;
}
else {
console.log("All conditions exhausted!");
break;
}
This is what I have in my service component.
patchTimeinStatus(timing: TimeInModel): Observable<TimeInModel> {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.patch(this.empApiUrl + '/time-in/', timing, options)
.map(this.extractData)
.catch(this.handleErrorObservable);
}
I get 405 method not allowed from the server. Apart from that, I doubt whether what am doing is correct. What is the correct approach to do the update using patch or put methods?

Angular Observable not doing the put call

I am working on my first Angular app, but am having a problem going an http.put call. Here is the function I call:
updateUser(columns, values) : Observable<boolean> | boolean {
const headers: Headers = new Headers(); // Need to set content type
headers.append('Content-Type', 'application/json; charset=utf-8');
headers.append('Authorization', `Bearer ${this.authenticationService.token}`);
const options = new RequestOptions({ headers: headers });console.log('test service');
return this.http.put(`${API_URL}users/${this.authenticationService.userId}`, JSON.stringify({ columns: columns, values: values }) , options)
.map((response: Response) => {
console.log('test service1');return Observable.of(true);
})
.catch(e => {
console.log('test service2');return Observable.of(false);
});
When I call the function test service prints to the console, but test service1 and test service2 never print out. I checked my express backend and chrome dev tools and the app is never making the put call to the backend. There are no errors in the console either. So I am missing something, but can't figure it out.
Thank you for any help
Edit: I'm wondering if the issue is because I am just calling this function in another function:
saveColumns(){
this.userService.updateUser('home_columns',this.columns_show);
localStorage.setItem('columns_show', JSON.stringify(this.columns_show) );
}
for http.get functions, I typically do something like this:
loadStudents(page: number, grade = []) {
if (grade.length != 0){
this.student_query_filter = { key:'grade_level',value:grade.join('||') };
} else {
this.student_query_filter = {};
}
this.studentService.getStudentsCount([{ key: 'last_name', value: this.student_search_filter },this.student_query_filter])
.subscribe(
total => this.total = total, //Assign returned student count to local property
err => { console.log(err); });
}
You want to pass the data as an object instead of with JSON.stringify.
You want to return the result from map, not another Observable. If you did want to return a different observable you should change map to switchMap.
The signature should be Observable<boolean> as that is what you are returning.
Be sure to check the developer console in your browser to see if the request is being sent and what the response is. It might be something simple like putting together the URL incorrectly (missing a / for example)
updateUser(columns, values) : Observable<boolean> {
const headers: Headers = new Headers(); // Need to set content type
headers.append('Content-Type', 'application/json; charset=utf-8');
headers.append('Authorization', `Bearer ${this.authenticationService.token}`);
const options = new RequestOptions({ headers: headers });
console.log('test service, sending to: ' + `${API_URL}users/${this.authenticationService.userId}`);
return this.http.put(`${API_URL}users/${this.authenticationService.userId}`, { columns: columns, values: values }, options)
.map((response: Response) => {
console.log('test service1');
return true;
})
.catch(e => {
console.log('test service2');
return false;
});
}
Edit
If your caller is not going to do anything with the result and you do not care what that result is then do not return an Observable at all. Change the return signature to void and execute a subscribe after the call to log the result.
this.http.put(`${API_URL}users/${this.authenticationService.userId}`, { columns: columns, values: values }, options)
.subscribe((response: Response) => {
console.log('test service1'); }
, e => {
console.log('test service2');
});

Categories

Resources