Angular 5 HttpClient: Send POST params as URL encoded string - javascript

Angular 5.0.1
I'm looking at the docs for Angular HttpClient: https://angular.io/guide/http,
but I can't seem to figure how to send POST params as a URLEncoded string instead of a JSON string. For instance, my Java http clients will send like this as default:
username=test%40test.com&password=Password1&rolename=Admin
But Angular wants to send as Json by default:
{"username":"test#test.com","password":"Password1","rolename":"Admin"}
Here's my code currently:
let body = {
username: "test#test.com",
password: "Password1",
rolename: "Admin"
};
let headers = new HttpHeaders();
headers = headers.set("Content-Type", "application/x-www-form-urlencoded");
this.http.post(this.baseUrl, body, {
headers: headers,
})
.subscribe(resp => {
console.log("response %o, ", resp);
});
I've also tried adding HttpParams:
let httpParams = new HttpParams();
httpParams.append("username", "test#test.com");
httpParams.append("password", "Password1");
httpParams.append("rolename", "Admin");
...
headers: headers,
params: httpParams
But HttpParams seem to have no effect.
Any idea how to URL encode the request instead of Json?

append() returns a new HttpParams object, so you'll need to make a slight modification to your httpParams code. Try this:
let httpParams = new HttpParams()
.append("username", "test#test.com")
.append("password", "Password1")
.append("rolename", "Admin");
In the code above, we chain our append calls, creating a new HttpParams object on each call. The last time we call append, the HttpParams object returned will contain all of the previously appended parameters.

That is because HttpParam is immutable.
You can read why here
In short:
let httpParams = new HttpParams()
.append("username", "test#test.com")
.append("password", "Password1")
.append("rolename", "Admin");
Because the app may retry requests, the interceptor chain may process
an individual request multiple times. If requests were mutable, a
retried request would be different than the original request.
Immutability ensures the interceptors see the same request for each
try.

Supply the HttpParams object as the body argument for post(). That way, you will send form data as the request body instead of JSON. Also, the params option is not needed.
Here's an example:
const body = new HttpParams()
.set('username', 'test#test.com')
.set('password', 'Password1')
.set('rolename', 'Admin');
this.httpClient.post(url, body, {
headers: new HttpHeaders()
.set('Content-Type', 'application/x-www-form-urlencoded')
});
The HttpParams class is immutable. All mutation operations will return a new instance. You'll need to chain the set() calls to preserve the previously added parameters. You can use both set() and append().

Related

how to get response headers?

I am using angular 15.0. I get a list of items from backend (asp.net core 5) with an item appended to the header. the get method in the client side service is:
/** GET Paged commodities from the server ===============================*/
getPagedCommodities(pageSize: number, pageNumber: number): Observable<CommodityForList[]> {
let params: HttpParams = new HttpParams();
params = params.append('pageSize', pageSize);
params = params.append('pageNumber', pageNumber);
const headers = new HttpHeaders({
observe: 'response'
});
return this.http.get<CommodityForList[]>(this.baseUrl + '/getPagedCommodities/', { headers, params });
}
this method sends two parameters to the server and gets commodities list along with totalCount to paging the list. I need the totalCount to set the length property of mat-paginator and commodities list as observable to provide a dynamic search for user. therefore, in the commoditiesList component, the snippet code for this purpose is:
commoditiesForList$!: Observable<CommodityForList[]>;
this.commoditiesForList$ = this.commoditiesService.getPagedCommodities(this.pageSize, this.pageIndex+1);
this.commoditiesForList$.subscribe( res => {
const totalCount = res.headers.get('X-Pagination');
})
but, here I have an error: Property 'headers' does not exist on type 'CommodityForList[]'.
when I change the type of commoditiesForList$ to HttpResponse<CommodityForList[]>, the error may be fixed, but receiving the commodities list as observable will have a problem. Is there a solution to get the commodities list as observable and read the totalCount separately from the header? thank you for your response.
See https://angular.io/guide/http#reading-the-full-response
You might need more information about the transaction than is contained in the response body. Sometimes servers return special headers or status codes to indicate certain conditions that are important to the application workflow.
getPagedCommodities(pageSize: number, pageNumber: number): Observable<HttpResponse<CommodityForList[]>> {
// ...
return this.http.get<CommodityForList[]>(this.baseUrl + '/getPagedCommodities/', { headers, params });
}
You can access the body in the body attribute.
See maybe also Angular: HttpClient read full response with Observable Array.
Given your example, you could use the next configuration on your HTTP request: {responseType: 'json', observe: 'events' }. See a working example here on stackblitz - one request
commoditiesForList$!: BehaviourSubject<CommodityForList[]>;
totalCount$!: BehaviourSubject<any>;
constructor(commoditiesService: CommoditiesService) {
this.commoditiesService.getPagedCommodities(this.pageSize, this.pageIndex+1).subscribe(res => {
this.commoditiesForList$.next(res.body)
this.totalCount$.next(headers.get('X-Pagination'))
})
}
Original Answer
Given your example, you could use the next configuration on your HTTP request: {responseType: 'json', observe: 'events' }. See a working example here on stackblitz - two requests - shared pipe
Edit: to avoid making two request, notice that GET request is using share operator from rxjs. Thanks Arber to notice it.
getPagedCommodities(pageSize: number, pageNumber: number): Observable<CommodityForList[]> {
let params: HttpParams = new HttpParams();
params = params.append('pageSize', pageSize);
params = params.append('pageNumber', pageNumber);
const headers = new HttpHeaders({
observe: 'response'
});
return this.http.get<CommodityForList[]>(this.baseUrl + '/getPagedCommodities/',
{ headers, params, responseType: 'json',observe: 'events'}).pipe(share());
}
then you will access data and headers in this way
commoditiesForList$!: Observable<CommodityForList[]>;
this.commoditiesForList$ = this.commoditiesService.getPagedCommodities(this.pageSize, this.pageIndex+1).pipe(
map((res) => (res as any).body));
this.totalCount$ = this.commoditiesService.getPagedCommodities(this.pageSize, this.pageIndex+1).pipe(
map((res) => (res as any).headers)), map(headers => headers.get('X-Pagination')));
Docs: https://angular.io/guide/http#requesting-data-from-a-server

Sending list of integers to REST API throws 415 (Unsupported Media Type)

I'm experimenting with reactjs and .net core web api, and I'm trying to send simple list of integer to the rest api. Throught postman I did it without any issues, but when I try to do it from my react js code I'm getting 415 error in a console:
This is how my method on the API looks:
// DELETE api/SiteUsers/delete
[HttpPost("delete")]
public async Task<ActionResult> DeleteSiteUsers(DeleteValuesDto deleteValues, CancellationToken cancellationToken)
{
return NoContent();
}
Here is how my DeleteValuesDto looks like:
On the frontend I made simple method which should make an api call to rest api:
const handleDelete = async () => {
var deleteValues = {
values: selectedRows,
};
await deleteSiteUser(deleteValues);
};
Here is my deleteSiteUser method on frontend:
export const deleteSiteUser = (deleteValues) => {
console.log('deleteValues', deleteValues);
return axiosWrapper.request({
url: `/SiteUsers/delete`,
method: 'POST',
deleteValues,
});
};
Here are the results of console log:
I thought this would be very simple, but I find out it isn't actually :/
You need to send the data in the named data property as per the documentation (https://github.com/axios/axios#request-config)
export const deleteSiteUser = (deleteValues) => {
console.log('deleteValues', deleteValues);
return axiosWrapper.request({
url: '/SiteUsers/delete',
method: 'POST',
data: deleteValues,
});
};
If you are not setting the data property, the payload is empty. The most likely outcome of this is, that the Conent-Type header is not set and thus Asp.Net cannot decide how to parse the request body and then throws that error. You could just check that in your developer console in Chrome, then set the data property and check the Content-Type header for that reqeust as well.
You're wrapping the IDs in an object that has a values field, but your endpoint is expecting a plain array.
Instead of wrapping selectedRows like you do with deleteValues, just pass it through directly.
const handleDelete = async () => {
await deleteSiteUser(selectedRows);
};

Values from web api are not being converted

I have a web api call. The property of checkNumber is a double on the web api side , however in my typescript model I need it to come in as a string. It is staying as a number even though my model clearly has it as a string variable.
Is there a way to get the conversion to automatically happen to string?
my web api call
public GetMyClass(myModel: MyClass): Observable<MyClass> {
let headers = new HttpHeaders();
headers.append("content-type", "application/json");
headers.append("accept", "application/json");
let options = { headers: headers };
return this.httpClient.post<MyClass>( url, myModel, options)
}
my model
export MyClass{
checkNumber?: string;
}
Typescript doesn't do auto conversion. It helps with type checking during development. At runtime, it just plain javascript.
You will need to define your own conversion.
public GetMyClass(myModel: MyClass): Observable<MyClass> {
let headers = new HttpHeaders();
headers.append("content-type", "application/json");
headers.append("accept", "application/json");
let options = { headers: headers };
return this.httpClient.post<MyClass>( url, myModel, options)
.pipe(
map(dataObject => {
let checkNumber = dataObject.checkNumber
return {
checkNumber: checkNumber ? dataObject.checkNumber.toString() : undefined,
...dataObject
}
})
)
}

How to pass array of objects to the graphql query from js client

I want pass to server array of object throw graphql API.
my query on the schema:
export const schema = buildSchema(`
type Query {
statistics(
modelId: String
picksEnds: [PickEnd]
)
}: Statistics
type PickEnd {
end: String
limit: float
}
...
`)
my js based query on clients side:
const createStatisticsQuery = (...) => {
return `query {
statistics(
modelId: "${modelId}",
picksEnds: ${JSON.stringify(myEnds)}
) ...
but get error from graphql:
message: "Syntax Error: Expected Name, found String "end""
snippet from request payload:
{"query":"query {\n statistics(\n modelId:
\"5ca0f4afb88b3a2e006faa0d\",\n
picksEnds:
[{\"end\":\"home\"},{\"end\":\"draw\"},{\"end\":\"away\"},{\"end\":\"under\",\"limit\":0.5},{\"end\":\"over\",\"limit\":0.5},{\"end\":\"under\",\"limit\":1.5 ...
While GraphQL syntax is similar to JSON, you cannot use JSON inside a GraphQL document, which is what you're doing by calling JSON.stringify and then inserting the result into your template string.
What GraphQL expects:
[{end:"foo",limit:2.0}]
What using stringify does:
[{"end":"foo","limit":2.0}]
Since this syntax is not valid, an error is thrown. The easiest way to get around this issue is to utilize variables to provide the needed input, since variable values are provided as JSON.
# Note: Replace PickEndInput with whatever your input type is actually called
query StatsQuery($id: String!, $ends: [PickEndInput!]!) {
statistics(modelId: $id, picksEnds: $ends) {
# ...
}
}
You can then construct a JavaScript object for the variables and pass it along to fetch, axios or whatever you're using to make your request. For example:
const variables = {
id: 'SOME_ID',
ends: [
{
end:'foo',
limit: 2.0,
},
],
}
const query = ...
fetch('SOME_ENDPOINT_URL', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ variables, query }),
})
Ideally, you'd want to use an existing client like Apollo to make these calls easier.

Why do I get an old object when creating a new object using let and new in a function within a service?

Alright, simple basic auth authentication service in angular2.
When a user logins first time, it works. But when he/she tries to login second time with a different account. I got double basic auth string in the request headers, it's like "Authorization:Basic YWRtaW46YWJjMTIz,Basic RMcasd9WJjMXoPj".
This is the service:
#Injectable()
export class AuthenticationService {
private url: string = 'http://localhost:8080/api/test';
private username: string;
private password: string;
private authenticationStatus: boolean = false;
constructor(private http: Http) { }
authentication(username: string, password: string): Promise<boolean> {
let headers = new Headers(); // <== previous headers object with old Authorization string get back from grave.
console.log(headers);
headers.append("Authorization", "Basic " + btoa(username + ":" + password));
headers.append("Content-Type","application/json;charset=utf-8");
return this.http.get(this.url, { headers: headers })
.toPromise()
.then(response => { ...
This is my first try angular/typescript app. I'm confused by not getting a brand new object when I use both let and new here. Is it because headersString within the header class is static? I did look into the angular headers class api doc. I tried call headers.delete("Authorization"); right after the let headers = new Headers();, The old Authorization header remains.
I'm confused by not getting a brand new object when I use both let and new here.
When you log an object the reference to the object is logged.
As an example:
let x = {};
console.log(foo);
x.foo = 'foo';
Now on your console if you look at x after you've mutated it, it will show the new stuff:
In short. Your code is correct. The console can lie to you.
Alternative answer: You are doing something that is not demonstrated in the code sample you have provided.

Categories

Resources