I am working on angular 4 validation. I have a reactive form that have two radio button and two form groups. If user select's first radio button, it will remove validation from second form group and add validation on first form group and when select's second radio button it will add validation on second form group and remove from first form group.
Below is my form group example
this.mainForm = this.formBuilder.group({
cardOrBank: new FormControl(''),
cardDetails: new FormGroup({
cardNo: new FormControl(''),
cvv: new FormControl('')
}),
bankDetails: new FormGroup({
accNo: new FormControl(''),
ifsc: new FormControl('')
})
});
HTML
<form [formGroup]="mainForm" (ngSubmit)="onFormSubmit()">
<div>
Select: <input type="radio" formControlName="cardOrBank"> card
<input type="radio" formControlName="cardOrBank"> bank
</div>
<div formGroupName="cardDetails">
<div>
Card No: <input formControlName="cardNo">
</div>
<div>
CVV: <input formControlName="cvv">
</div>
</div>
<div formGroupName="bankDetails">
<div>
ACC No: <input formControlName="accNo">
</div>
<div>
IFSC: <input formControlName="ifsc">
</div>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
If select card from radio button, it will add validation on cardDetails form and remove validation from bankDetails and vice versa.
P.S: Form fields may be more according to the requirement.
Thanks.
After doing a lot of work finally i was able to achieve this.
Below are the changes you need to make in your code :
// in component.ts file :
// Write two genric methods which sets and clears the validator
setRequireValidator(form:any){
for (const field in form.controls) { // 'field' is a string
let con = form.get(field); // 'control' is a FormControl
con.setValidators([Validators.required]);
con.updateValueAndValidity();
}
}
removeValidator(form:any){
console.log('form contro',form);
for (const field in form.controls) { // 'field' is a string
let con = form.get(field); // 'control' is a FormControl
con.clearValidators();
con.updateValueAndValidity();
}
// while initiallizing the form ragister the event for value changed on `cardOrBank`
// check which value user has selected and accordingly toggle between them
this.mainForm.get('cardOrBank').valueChanges.subscribe((val) => {
const cardControl = this.mainForm.get('cardDetails');
const bankControl = this.mainForm.get('bankDetails');
if(val === 'card') {
alert('card sletecd')
this.removeValidator(bankControl);
this.setRequireValidator(cardControl);
} else{
alert('bank sletecd')
this.removeValidator(cardControl);
this.setRequireValidator(bankControl);
}
});
<!-- In component.html file-->
<form [formGroup]="mainForm" (ngSubmit)="onFormSubmit()">
<div>
<label>
<!-- You missed value attribute -->
<input type="radio" value="card" formControlName="cardOrBank">
<span>Card</span>
</label>
<label>
<input type="radio" value="bank" formControlName="cardOrBank">
<span>Bank</span>
</label>
</div>
<div formGroupName="cardDetails">
<div>
Card No: <input formControlName="cardNo">
</div>
<div>
CVV: <input formControlName="cvv">
</div>
</div>
<div formGroupName="bankDetails">
<div>
ACC No: <input formControlName="accNo">
</div>
<div>
IFSC: <input formControlName="ifsc">
</div>
</div>
<div>
<button type="submit" [disabled]="!mainForm.valid">Submit</button>
</div>
</form>
Here is the Working Example of your requirement :
Working demo
you can do like this
addValidation() {
this.form.get('title').setValidators([Validators.required, Validators.minLength(3)]);
this.form.get('title').updateValueAndValidity();
}
removeValidation() {
this.form.get('title').clearValidators();
this.form.get('title').updateValueAndValidity();
}
You can do two things, either you can put explicit method or you can subscribe to the changes happens to formControlName to perform this validator switch.
if you want to subscribe then subscribe on ngOnInit() lifecycle hook:
ngOnInit() {
this.mainForm.get('cardDetails').valueChanges.subscribe((val) => {
if(val === 'card') {
this.mainForm.get('cardDetails').setValidators(Validators.required);
} else {
this.mainForm.get('bankDetails').removeValidators(Validators.required);
}
this.mainForm.get('bankDetails').updateValueAndValidity();
});
}
**add Value attribute in the html.**
**2nd Option :**
Select: <input type="radio" formControlName="cardOrBank (change)="changedPayment('card')"> card
<input type="radio" formControlName="cardOrBank (change)="changedPayment('bank')> bank
changedPayment(val) {
if(val === 'card') {
this.mainForm.get('cardDetails').setValidators(Validators.required);
} else {
this.mainForm.get('bankDetails').removeValidators(Validators.required);
}
this.mainForm.get('bankDetails').updateValueAndValidity();
}
I assume that cardOrBank form control has two values 'card' and 'bank'. In your ngOnInit, you should subscribe the valueChanges of your radio button form control.
ngOnInit() {
this.mainForm.get('cardDetails').valueChanges.subscribe((cardOrBank) => {
if(cardOrBank === 'card') {
this.mainForm.get('cardDetails').setValidators(Validators.required);
this.mainForm.get('bankDetails').removeValidators(Validators.required);
} else {
this.mainForm.get('bankDetails').setValidators(Validators.required);
this.mainForm.get('cardDetails').removeValidators(Validators.required);
}
this.mainForm.get('bankDetails')updateValueAndValidity();
this.mainForm.get('cardDetails')updateValueAndValidity();
});
}
Your radio button should have a value attribute
<div>
Select: <input type="radio" value="card" formControlName="cardOrBank"> card
<input type="radio" value="bank" formControlName="cardOrBank"> bank
</div>
Related
I need to reset only the form fields that are changed (reset to previous value).
I tried to use the reset but it completely resets the entire form and I don't need this.
How can I do this?
function clearResult() {
document.getElementById("save").reset();
}
<div class = "container">
<form method="post" id="save" onload="onLoad()">
<div class="field">
<label for="id"> ID:</label>
<input type="number" id="id" name="id" />
</div>
<div class="field">
<label for="type"> Fragment Type: </label>
<input type="text" id="type" name="type" />
</div>
<div class="button">
<button type="submit" class="full">Save changes</button>
<input type="button" value="Cancel" onclick="clearResult()" />
</div>
</form>
</div>
First of all you should to store the predefined values of form elements to set them back when you want to reset them back.
Then you can use this globally defined initial values to set them back whnever reset event occurs.
var id, type;
const form = document.getElementById('save');
document.onload = (event) => {
id = form.id.value;
type = form.type.value;
};
function clearResult() {
form.id.value = id;
form.type.value = type;
};
It was simple. When page load just get values from that fields.
Make with document.onload:
var firstValue = document.getElementById("yourFieldId").value;
After form submit get values like below:
var currentValue = document.getElementById("yourFieldId").value;
And after submit in reset check if CURRENT VALUES equal with VALUES FROM FIRST TIME
Example
if(firstValue != currentValue){
document.getElementById("yourFieldId").value = firstValue;
}
I have a Bootstrap form modal being used for a webinar signup. I've removed the other fields, but I have multiple text fields that are being captured and pushed to a CMS.
It's just these 3 checkboxes that I can't seem to get to push data across correctly. The checkboxes are recording all as "on" if the form is submitted once, but if the form is submitted a second time then the checkboxes that are checked/not checked will push through "on" and "off" accordingly. So it works on the second submit, but not on the first. Any help would be greatly appreciated. I'm new to javascript and jquery, so i'm now at the point of guessing.
$(document).ready(function() {
$('.register-webinar').click(function() {
checked = $("input[type=checkbox]:checked").length;
if (!checked) {
alert("You must select at least one session.");
return false;
}
});
$('input.webinar').change(function() {
if ($(this).is(":checked")) {
$(this).val("on")
} else {
$(this).val("off")
}
});
$('#railway_form').on("submit", function(e) {
e.preventDefault();
setTimeout(AoProcessForm(this), 200);
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css">
<div class="form-popup-bg">
<div class="form-container">
<button id="btnCloseForm" class="close-button">X</button>
<h1 style="text-align:center;">Register your attendance</h1>
<p style="text-align:center;">Please select which session/s you wish to attend.</p>
<form name="railway_webinar" id="railway_form" class="railway_webinar" action="">
<div class="form-group">
<input type="checkbox" id="Webinar_1" name="Webinar_1" class="webinar">
<label for="Webinar_1">webinar 1</label><br/>
<input type="checkbox" id="Webinar_2" name="Webinar_2" class="webinar">
<label for="Webinar_2">webinar 2</label><br/>
<input type="checkbox" id="Webinar_3" name="Webinar_3" class="webinar">
<label for="Webinar_3">webinar 3</label>
</div>
<input type="submit" value="Reserve my seat!" class="form-block-cta register-webinar" />
</form>
</div>
</div>
Figured it out. All I needed to do was set the checkbox value to "off", as they would all be "on" by default.
I had condition in my reactive form where one checkbox selected, submit button will be enabled, and if there are none checkbox selected, it will remain disabled, the problem is I had selectAll function, which if I clicked, it will selected all the checkbox and enabled submit button, then if I unselect individual checkbox after select all function, the submit button should be enabled until all the checkbox is unselect, this is what I had tried:
ts file
selectAll() {
this.formReceivedSummons.controls.map(value => value.get('isChecked').setValue(true));
return this.disabledButton = false;
}
changeCheck(event){
this.disabledButton = !event.target.checked;
}
html file
<div *ngIf="isShowResponse">
<p>Inquiry Response</p>
<form [formGroup]="form" (ngSubmit)="submitSelectedCheckboxes()">
<ng-container formArrayName="receivedSummons" *ngFor="let
summon of formReceivedSummons.controls; let i = index">
<ng-container [formGroupName]="i">
<input type="checkbox" formControlName="isChecked"
(change)="changeCheck($event)">
{{ summon.get('isChecked').value ? 'selected' : 'select' }}
</ng-container>
</ng-container>
<button [disabled]="disabledButton">Submit</button>
</form>
<button (click)="selectAll()">Select All</button>
</div>
supposed to be after select all function, submit button will enabled until all checkbox is unselected individually then it will disabled, this is my stackblitz demo, I could use any suggestion to solve this problem,
the "clasic solution" is make a custom error validator
Imagine that you has some like
options=["option 1","option 2","option 2"]
form=new FormGroup({
prop1:new FormControl(),
receivedSummons:new FormArray(this.options.map(x=>new FormControl()),this.selectAtLeastOne())
})
selectAtLeastOne()
{
return (formArray:FormArray)=>{
return formArray.value.some(x=>x)?null:{error:"At least one"}
}
}
You only need
<button [disabled]="form.invalid">submit</button>
The completed form is like
<div [formGroup]="form">
<input formControlName="prop1">
<div formArrayName="receivedSummons">
<div *ngFor="let control of form.get('receivedSummons').controls;let i=index" >
<input type="checkbox" [formControlName]="i">{{options[i]}}
</div>
</div>
<button [disabled]="form.invalid">submit</button>
</div>
NOTE: I choose use a formArray of FormControls, not a FormArray of formGroups, if you want to use a formArray of FormGroups, the code becomes like
form2=new FormGroup({
prop1:new FormControl(),
receivedSummons:new FormArray(
this.options.map(x=>new FormGroup({
isChecked:new FormControl()
})),this.selectAtLeastOne2())
})
selectAtLeastOne2()
{
return (formArray:FormArray)=>{
return formArray.value.some(x=>x.isChecked)?null:{error:"At least one"}
}
}
And the .html
<div [formGroup]="form2">
<input formControlName="prop1">
<div formArrayName="receivedSummons">
<div *ngFor="let control of form.get('receivedSummons').controls;let i=index" [formGroupName]="i" >
<input type="checkbox" formControlName="isChecked">{{options[i]}}
</div>
</div>
<button [disabled]="form2.invalid">submit</button>
</div>
You can see the two forms in stackblitz
Updated I add two funtions to check/uncheck all
selectAll(select: boolean) {
this.form.get("receivedSummons").setValue(this.options.map(x => select));
}
selectAll2(select: boolean) {
this.form2.get("receivedSummons").setValue(
this.options.map(x => {
return { isChecked: select };
})
);
}
The check to check/uncheck all like (see as I use a refernce variable to pass if is checkd or not the checkBox
<input #check1 type="checkbox" (change)="selectAll(check1.checked)">Check All
In html, define a reference in input "#checkboxes"
<input type="checkbox" formControlName="isChecked" #checkboxes (click)="changeCheck()">
In ts,
We can get all checkbox values using #ViewChildren
#ViewChildren("checkboxes") checkboxes: QueryList<ElementRef>;
ViewChildren, QueryList, ElementRef to be imported from 'angular/core'
changeCheck(){
let flag= true;
this.checkboxes.forEach((element) => { // loop all checkboxes to find checked boxes
if(element.nativeElement.checked){
flag=false // if atleast one checkbox checked, put the flag flase
}
});
this.disabledButton = flag;
}
Working code at Stackblitz
Try the below code:
changeCheck(event){
let flag = true
for (let summon of this.formReceivedSummons.controls){
if(summon.get('isChecked').value) {
flag = false
}
}
this.disabledButton = flag
}
I have a component with a reactive form in it like so...
form component.html
<form [formGroup]="form" class="do-form">
<div formGroupName="dot">
<div class="do-form__container">
<div class="do-form__group">
<label for="day">Day</label>
<input id="day" type="number" placeholder="XX" class="do-form__group__control" formControlName="day" />
</div>
<div class="do-form__group">
<label for="month">Month</label>
<input id="month" type="number" placeholder="XX" class="do-form__group__control" formControlName="month" />
</div>
<div class="do-form__group">
<label for="year">Year</label>
<input id="year" type="number" placeholder="XXXX" class="do-form__group__control" formControlName="year" />
</div>
</div>
<div class="do-form__errors" *ngIf="isValid()">
<p>Please enter a valid date</p>
</div>
</div>
</form>
and in my form.component.ts
form = this.fb.group({
dot: this.fb.group({
day: ['',
[
Validators.required,
Validators.min(1),
Validators.max(31)
]],
month: ['',
[
Validators.required,
Validators.min(1),
Validators.max(12)
]],
year: ['2018',
[
Validators.required,
Validators.min(1918),
Validators.max(2018)
]]
})
});
isValid() {
return (
!this.form.valid &&
this.form.get('dot.day').touched &&
this.form.get('dot.month').touched &&
this.form.get('dot.year').touched
);
}
Now I have a separate page (app.component.html) like so
<app-form #formTool></app-form>
<button class="full-width-btn" (click)="next(); form.sendResult();">SEND</button>
app.component.ts
import { formComponent } from '../form-component/form-component.ts'
export ...
#ViewChild(formComponent) form;
Now basically I want to disable the send button until the form in the app form component is valid.
I'm not entirely sure how to do this. I thought about storing a valid event on a shared server but I'm not sure how I can store a valid event in a service. I saw that with non-reactive forms you can just have a button that uses the same ngModel but once again not sure if that would work in this case.
Any help would be appreciated!
EDIT
I have tried [disabled]="form.invalid" and [disabled]="!isValid()" but I am still able to click the button
I have also tried using [disabled]=!form.isValid() and [disabled]=!form.form.isValid()
Thanks
You are really close. Here is the only thing you need to add:
<app-form #formTool></app-form>
<button class="full-width-btn" [disabled]="!isValid()" (click)="next(); form.sendResult();">SEND</button>
In form component you could define an event #Output formValid = new EventEmitter().
Then you can listen to changes in input fields (on keypress or so) and on any change check the validity and if the form is valid, emit an event: formValid.emit().
In the app component define formIsValid = false, and on the app-form element you can listen to the formValid event:
< app-form (formValid)="formIsValid = true">
(or some function in app.component.ts instead of the inline code).
And finally on button
< button [disabled]="!formIsValid">
I am dynamically rendering pages using Handlebars.js, and I have a "quiz-form template" with the following code:
<div id="right-pane">
<script type="text/x-handlebars-template" id="quiz-form-template">
<form class="cf" id="quiz-form">
<h2>Create a <span>quiz</span></h2>
<p>Enter a quiz name and description to get started.</p>
<div>
<input type="text" name="quiz-name" placeholder="Quiz Name" />
</div>
<div>
<textarea rows="5" cols="40" name="quiz-description"
placeholder="Description"></textarea>
</div>
<div id="checkbox">
<input type="checkbox" name="is_random" /> Create randomly generated quiz <br/>
<input type="checkbox" name="is_single_page" /> Render quiz on a single page <br/>
<input type="checkbox" name="is_immediate"/> Give immediate feedback <br/>
</div>
<input type="submit" class="btn" value="Add Questions" />
</form>
</script>
</div>
I am running into two problems that I have been trying to debug to no avail. After rendering this page on my html, when I click the checkboxes, they do not get checked at all. It seems like I click and it "almost bounces off".
Additionally when I click the submit button, it is not being listened to. I am console.log"ging" to check and their is not output. Here is my event listener:
rightPane.addEventListener("submit", function(event) {
console.log( event.target );
event.preventDefault;
if ( event.target.value === "Add Questions" ) {
//DOM elements
newQuizName = document.querySelector('#quiz-form input');
newDescription = document.querySelector('#quiz-form textarea');
randomStatus = document.querySelector('#quiz-form input[name="is_random"]');
singlePageStatus = document.querySelector('#quiz-form input[name="is_single_page"]');
immediateStatus = document.querySelector('#quiz-form input[name="is_immediate"]');
var pendingQuiz = getPendingQuiz();
pendingQuizMetaData = {name: newQuizName.value, description: newDescription.value,
random: randomStatus.checked, singlePage: singlePageStatus.checked,
immediate: immediateStatus.checked
pendingQuiz = { metadata: pendingQuizMetaData, questions: [] };
updatePendingQuiz( pendingQuiz );
rightPane.innerHTML = templates.renderQuestionType();
newQuestion = "";
newSubject = "";
// }
// Since add questions is clicked, we should send the user to select question type
// we'll need to render html on the right pane
}
});
Any input would be greatly appreciated.