How to post form data to a server expecting a regular HTML form using angular reactive? - html

I'm replacing a static form that sends data to an old perl script with an angular reactive form. I can't figure out how to format the form data and send it using HttpClient.post. A lot of examples I'm seeing for posting this way actually create the data object in the request itself, not coming from an actual form, so I'm stumped
I've tried posting the data as just the form, as JSON (I'm not sure the server is expecting JSON, it's really old), sending the form's data object in the controller
the original static form <form> tag:
<form id="HTML_FORM" method="POST" role="form" action="https://sos.cnm.edu/MRcgi/MRProcessIncomingForms.pl" >
This was just submitted using a regular HTML submit input, and that works fine.
My new template:
<form [formGroup]="opieForm" (ngSubmit)="onSubmit()">
<input type="text" class="form-control" id="TITLE" formControlName="TITLE" #TITLE placeholder="required" >
...
<button class="btn btn-primary" [disabled]="!opieForm.valid" type="submit">Submit form</button>
</form>
The controller (parts)
opieForm: FormGroup;
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded'
})};
this.opieForm = this.fb.group({
TITLE: [ '', Validators.required ],
// lots of other inputs
})
onSubmit = () => {
console.warn(this.opieForm.value);
return this.http.post('https://sos.cnm.edu/MRcgi/MRProcessIncomingForms.pl', this.opieForm.value, this.httpOptions)
.subscribe(res => console.log(res), error => console.error(error));
}
The console.warn(this.opieForm.value); produces output:
{TITLE: "test", First__bName: "test", Last__bName: "test", Email__bAddress: "test#test.com", dateNeeded: {…}, …}
The error that's returned
HttpErrorResponse {headers: HttpHeaders, status: 200, statusText: "OK", url: "https://sos.cnm.edu/MRcgi/MRProcessIncomingForms.pl", ok: false, …}
error: {error: SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse (<anonymous>) at XMLHtt…, text: "<!DOCTYPE>↵ <HTML>↵ <!-- FP VERSION: 11.6.04 -->↵↵…ipt language = "javascript">self.close()</script>"}
headers: HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
message: "Http failure during parsing for https://sos.cnm.edu/MRcgi/MRProcessIncomingForms.pl"
name: "HttpErrorResponse"
ok: false
status: 200
statusText: "OK"
url: "https://sos.cnm.edu/MRcgi/MRProcessIncomingForms.pl"
Error text:
SyntaxError: Unexpected token < in JSON at position 0↵ at JSON.parse (<anonymous>)↵ at XMLHttpRequest.onLoad (http://localhost:4200/vendor.js:7639:51)↵ at ZoneDelegate.push.
...
When the form submits correctly it redirects to a success page and sends an email.

Related

Reactive form fields are not updated with setValue or patchValue

Following is my piece of code, I have simplified the code for purpose of brevity.
ngOnInit() {
//intialize form fields
this.form = this.builder.group({
name: '',
age: '',
location: '',
});
//Call to the service
this.dataService.getDetails().subscribe(
(data) => {
this.dataArray = data;
if (this.dataArray[this.count].status === 'OK') {
let docs = {};
this.someService.getDocs(this.dataArray[this.count].id).subscribe(
(data) => {
docs = data;
console.log("docs: ", docs);
this.setFormValues(docs);//set form values
},
(err) => {
console.log(err);
console.log('Something happened');
}
);
}
},
(err) => {
console.log(err);
console.log('Something happened',err);
}
);
}
Now in setFormValues() I have printed the values of the fields and its working fine up to that point but next when I try to bind the values to the form , with either setValue or patchValue, it simply does not update the form with the fetched values from service.
Some more code in this regard
public setFormValues(doc: DocsDTO) {
if (doc!= null) {
console.log("setFormValues", doc);
this.form.patchValue({
name: doc.name == null ? '' : doc.name.text,
age: doc.age == null ? '' : doc.age.text,
location: doc.location == null ? '' : doc.location.text,
})
}
}
Here is how my form looks like
<form [formGroup]="form">
<mat-card-content>
<input placeholder="name" [formControl]="name" id="name"
ngDefaultControl></input>
<input placeholder="age" [formControl]="age" id="age" ngDefaultControl></input>
</mat-card-content>
</mat-card>
</form>
Note: When I do not use FormBuilder and intialize form fields with FormControl and set form values with this.name.setValue() then it works fine.
I am pretty new to angular , I am not sure what am I doing wrong here.
This looks fine to me except the place where you are setting up the pathvalue:
The doc.name.text doesn;t look right to me. You should try
this.form.patchValue({
name: !!doc.name ? doc.name : '',
age: !!doc.age ? doc.age: '',
location: !!doc.location ? doc.location : '',
})
This will update our FormControl instances, simple! Also I think we do not need teh conditional setting here as :
set pathvalue throws no errors due to the if check inside the
Object.keys loop. Some might say it’s a safe $apply, just kidding.
It’ll allow you to set values that exist and it will ignore ones that
do not exist in the current iterated control
On the other hand setValue is a “more safe” way to do things. It’ll error for props that do not exist.
If you add the formControlName properly, it would work:
<input placeholder="name" formControlName="name" id="name"
ngDefaultControl>
<input placeholder="age" formControlName="age" id="age" ngDefaultControl>
take a look at the stackBlitz that i did for you here:

passing multiple arguments or object in (click)

Problem is to pass the object or multiple arguments from template to component and use them to add data to API.
task.service.ts
addTasks(task: Task): Observable<Task>{
let headers = new Headers({'Content-type': 'application/json'});
let options = new RequestOptions({ headers: headers });
return this.http.post(this.tasksUrl, {task}, options)
.map(this.extractData)
.catch(this.handleError);
}
task.component.ts
addTasks(task){
this.taskService.addTasks(task)
.subscribe(
task => this.tasks.push(task),
error => this.errorMessage = <any> error
);
}
Template Inputs:
<input #todoTime type="text" class="form-control">
<input #todoName type="text" class="form-control">
Template Button:
<button name="todoAdd" (click)="addTasks({name: todoName.value, time: todoTime.value}); todoName.value='',todoTime.value='' ">add</button>
Replace the comman(,) with a semicolon when you are handling the click event for the button. That should work.
<button name="todoAdd" (click)="addTasks({name: todoName.value, time: todoTime.value}); todoName.value=''; todoTime.value='' ">add</button>
I have created this simple Plnkr that shows object is getting passed to addTasks() function.

How can I display the errors from an POST Response in the UI?

Upon an invalid login, the following response is received.
Response {
_body: "{
"email": ["The email field is required."],
"password":["The password field is required."]
}",
status: 422, ok: false, statusText: "Unprocessable Entity", headers: Headers
}
I want to display the errors to the user in the UI. Here's my implementation of the observable.
Class
export class LoginPage
{
signInData: any[];
errors: any[];
...
this.userService.signIn(form.value.email, form.value.password)
.subscribe(
data => this.signedInData = data,
error => this.errors = error._body
);
}
I simply set the retrieved _body object to this.errors. If I try to display the errors as follows,
Template
<ul *ngFor="let error of errors">
<li>{{error}}</li>
</ul>
I get the following error.
Error in ./LoginPage class LoginPage - inline template:30:19 caused by: Cannot find a differ supporting object '{"email":["The email field is required."],"password":["The password field is required."]}' of type 'string'. NgFor only supports binding to Iterables such as Arrays.
How do I handle this?
UPDATE
If I change the subscription to:
this.userService.signIn(form.value.email, form.value.password)
.subscribe(
data => this.signedInData = data,
error => this.errors.push(error._body)
);
<ul *ngFor="let error of errors">
<li>{{error}}</li>
</ul>
It successfully prints the error object. Now I would need to access the values in the object, remove the braces and print the values. How?
WORKING CODE
Thanks to #echonax, I am able to parse the data to JSON, check if it exists and then push into the errors array one by one.
this.userService.signIn(form.value.email, form.value.password)
.subscribe(
data => this.signedInData = data,
error =>
{
let err = JSON.parse(error._body);
if(err && err.email && err.email[0]){
this.errors.push(err.email[0]);
}
if(err && err.password && err.password[0]){
this.errors.push(err.password[0]);
}
}
);
<ul *ngFor="let error of errors">
<li>{{error}}</li>
</ul>
*ngFor only supports iterables like Arrays
Your error.body is an object
"{
"email": ["The email field is required."],
"password":["The password field is required."]
}"
So it's not compatible to use with *ngFor
Instead of using
<ul *ngFor="let error of errors">
<li>{{error}}</li>
</ul>
Try:
<span>{{errors | json}}</span>
like #Aravind has mentioned
If you want to select them individually, something like this might work:
<span>{{errors?.email[0]}}</span>
<span>{{errors?.password[0]}}</span>
Update
if
this.errors.push(error._body) works
in order to get the values individually; try:
this.userService.signIn(form.value.email, form.value.password)
.subscribe(
(data) => this.signedInData = data,
(error) => {
error = JSON.parse(error._body);
if(error._body && error._body.email && error._body.email[0]){
this.errors.push(error._body.email[0]);
}
if(error._body && error._body.password && error._body.password[0]){
this.errors.push(error._body.password[0]);
}
}
);
<ul *ngFor="let error of errors">
<li>{{error}}</li>
</ul>
You are referring to wrong variable
<ul *ngFor="let error of error">
<li>{{error.email}}</li>
</ul>
You are storing the errors in the errors variable and you refer to signInErrors.
Note: You should display it as json as you cannot predict what will be error from service.
<span>{{error |json }}</span>
Update 1 :
You should be converting to json() as below
this.userService.signIn(form.value.email, form.value.password)
.subscribe(
data => this.signedInData = data,
error => this.errors = JSON.parse(error._body)
);

Angular2 Re-enable submit button after it has been disabled in HTTP post request

I am currently doing a profile page for a website. There is a submit button at the bottom of the page. When user is viewing the page, the submit button is disabled as nothing has changed.
When something is modified and its valid, the button will be clickable. Then after submit through HTTP Post, if it is successful, there is a flag to make it unclickable, and a success message will be displayed for the user. Everything above this is fine.
However, the problem comes in when the user change the content again. Because there used to be a flag and the flag is always disabling the button.
So to solve the issue, i think there could be 2 approaches. One is to toggle the flag once the content is changed again. One is to Disable the button in a totally different fashion. But i am not sure how to do either of them.
TS code:
#Component({
moduleId: module.id,
selector: 'profile',
templateUrl: 'profile.component.html',
})
export class ProfileComponent{
name: string;
email: string;
updateSucc : boolean = false;
profileForm : FormGroup;
constructor(fb: FormBuilder, private http:Http) {
this.profileForm = fb.group({
'name' : ['', Validators.required],
'email': [''],
})
submitForm(value: any){
let form = {
'updates': {
'name' : value.name
}
}
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers,
withCredentials: true });
this.http.post('http://SERVER.API?email='+localStorage.getItem('email'), form, options).subscribe(
(res:any)=>{
this.updateSucc = true;
},
error => {
console.log(error);
this.backendService.checkLogIn(error.status);
}
)
}
}
HTML Code:
<div class="container">
<h1>Profile</h1>
<div *ngIf = "updateSucc" class="alert alert-success">
Update successful!
</div>
<form [formGroup]="profileForm" (ngSubmit)="submitForm(profileForm.value)">
<div class="form-group"
[ngClass]="{'has-error':!profileForm.controls['name'].valid && profileForm.controls['name'].touched}"
>
<label for="name">Name</label>
<input type="text" class="form-control" id="name"
[formControl]="profileForm.controls['name']"
[(ngModel)] = 'name' name = 'name'
>
</div>
<div class="form-group"
[ngClass]="{'has-error':!profileForm.controls['email'].valid && profileForm.controls['email'].touched}"
>
<label for="email">Email</label>
<input type="text" class="form-control" id="email"
[formControl]="profileForm.controls['email']"
[(ngModel)] = 'email' name = 'email'
readonly
>
</div>
<button type="submit" class="btn btn-default"
[disabled]="!profileForm.valid
|| !(profileForm.controls['name'].dirty
|| profileForm.controls['email'].dirty
)
|| updateSucc
">Submit</button>
</form>
</div>
The variable updateSucc is the flag.
I understand that the form, when first shows up, may contain some content (let's call this ORIGINAL-CONTENT).
How should the app behave when the user changes the form data in a valid way (so that the button gets enabled) and then changes back again and resets the ORIGINAL-CONTENT?
If in this case the button must be disabled, then you need to link the [disabled] attribute to a method that compares the ORIGINAL-CONTENT (which must be stored in a variable in ngOnInit) with the current content of the form.
Otherwise I would keep your implementation (maybe changing the name of your variable flag from updateSucc to submitButtonDisabled) and would set the value of the flag to true with an onChange() method triggered by the onChange event of the form.
I hope this helps

How Set Authorization headers at HTML Form or at A href

I have this code:
$.ajax({
url: "http://localhost:15797/api/values",
type: 'get',
contentType: 'application/json',
headers: {
"Authorization": "Bearer " + token
}
})
works fine, but I want to do that without using Ajax, I want something like that:
<form action="http://localhost:15797/api/values" method="get">
<input type="hidden" name="headers[Authorization]" value="Bearer token" />
<input type="submit" />
</form>
Is it possible? Or just do something like that without XMLHttpRequest? How?
You need to send the Authorization argument in the HTTP request header, it's imposed by OAuth flows. Of course you can change it by making changes in OAuth server's code but if you've got no control on the OAuth server's code it's not possible.
So answering your question, no you can't send them with the form's posted data. However, obviously you can put them in the hidden field and write a JS code to read it from the field and put it in the request header.
e.g.
HTML:
<input id="tokenField" type="hidden" />
<input id="submitButton" type="button" />
Javascript:
$('#submitButton').on('click',function(){
$.ajax({
url: "http://localhost:15797/api/values",
type: 'GET',
contentType: 'application/json',
headers: {
"Authorization": "Bearer " + $('#tokenField').val()
},
async: false
}});
Notice the async: false makes your call synchronous, just like a submit. And if you need to post other data to the server you can change the type: 'GET' to type: 'POST' and add another field named data and pass your form data through its value :
<input id="firstName" type="text" />
<input id="lastName" type="text" />
<input id="tokenField" type="hidden" />
<input id="submitButton" type="button" />
$('#submitButton').on('click',function(){
$.ajax({
url: "http://localhost:15797/api/values",
type: 'POST',
data: {
firstName: $('#firstName').val(),
lastName: $('#lastName').val()
},
contentType: 'application/json',
headers: {
"Authorization": "Bearer " + $('#tokenField').val()
},
async: false
}});

Resources