Angular 2: Form valid returns false until all fields are populated - forms

I am using reactive forms.
I have a submit button that should be disabled until the form is valid:
<button type="submit" [disabled]="!_searchForm.valid && !_searchForm.pristine">Submit</button>
My fields can be toggled on/off using bools:
showName: boolean = true;
showPhone: boolean = true;
showCellphone: boolean = true;
And this is my validation rules:
this._searchForm = this._formBuilder.group({
name: [{value: '', disabled: !this.showName}, Validators.compose([Validators.minLength(3), Validators.maxLength(50), Validators.pattern('^[a-zA-Z]+$')])],
phone: [{value: '', disabled: !this.showPhone}, Validators.compose([Validators.minLength(3), Validators.maxLength(50), Validators.pattern('^[0-9-]+$')])],
cellphone: [{value: '', disabled: !this.showCellphone}, Validators.compose([Validators.minLength(3), Validators.maxLength(50), Validators.pattern('^[0-9]+$')])]
});
And finally, this is how each field is displayed in the HTML:
<div class="form-group" [ngClass]="{'has-danger': _searchForm.controls.name.errors && !_searchForm.controls.name.pristine}">
<label for="name">Name:</label>
<div class="input-group">
<span class="input-group-addon">
<div class="onofwrapper">
<div class="onoffswitch">
<input id="toggleName" type="checkbox" class="onoffswitch-checkbox" (click)='toggleName()' [checked]="showName">
<label class="onoffswitch-label" for="toggleName"></label>
</div>
</div>
</span>
<input type="text" formControlName="name" class="form-control" [ngClass]="{'form-control-danger': _searchForm.controls.name.errors && !_searchForm.controls.name.pristine}" autocomplete="off" spellcheck="false">
</div>
<div *ngIf="_searchForm.controls.name.errors && !_searchForm.controls.name.pristine" class="form-control-feedback">Error message</div>
If I don't touch the form I can submit, so pristine seems to be working.
The problem is, I can't enter text in just a single field and submit. If I enter text in one field, I have to enter text in all of them to be able to submit, or else _searchForm.valid won't return true even though I am not using Validators.required on all fields.
I have verified that each input "ships its own value", by removing the [disabled="!_searchForm.valid" line, and then just dumping out the values in my submit function like this:
console.log('Name: ' + this._searchForm.value.name);
console.log('Phone: ' + this._searchForm.value.phone);
console.log('Cellphone: ' + this._searchForm.value.cellphone);
What am I doing wrong?
Why does .valid require all fields in the form?

If you disable or enable an input, then, you need a function:
enableDisableInput(inputName: string): void {
if(!this._searchForm.controls[inputName].disabled) {
this._searchForm.controls[inputName].clearValidators();
this._searchForm.controls[inputName].disable();
} else {
this._searchForm.controls[inputName].setValidators(Validators.compose([Validators.minLength(3), Validators.maxLength(50), Validators.pattern('^[0-9]+$')])]);
}
this._searchForm.controls[inputName].updateValueAndValidity();
}
Invoked <input id="toggleName" type="checkbox" class="onoffswitch-checkbox" (click)='enableDisableInput("name")' [checked]="showName">
The manner in which the form building and validation is done with Reactive Forms means that you must manually clear and add validation even on disabled items (there may be plans to change this as it is a not uncommon complaint on the angular github). This is a code oriented and driven manner of forms and needs to be treated as such for the time being.
Whether or not the inputs are required is moot if they have an unmet minimum length. Reference https://github.com/angular/angular/pull/11450 which was recently included in Angular 2.0.2 https://github.com/angular/angular/blob/master/CHANGELOG.md
For forms driven it looks like they have a correction in 2.1.0 where those fields are optional with pattern and minlength but I don't know if that's also in Reactive Forms or not.

It turns out that this was a problem with input type="number". The fields containing phone and cellphone values was only supposed to contain numbers, so in addition to the Validators.pattern('^[0-9-]+$') validation I also gave them the input type of number.
As soon as that was changed into text, everything worked like expected.

I had the same issue with the Angular reactive forms. I was disabling and enabling my form controls based on some logic. It turns out I was disabling, but not enabling back the form controls, so I was getting a form.valid as false. Apparently in Angular's reactive forms a form with disabled fields is invalid and the docs are not mentioning this default behavior.

Related

Angular 2 / PrimeNG - Expression has changed after it was checked. Binding NgModel on last invalid form control

I'm having a problem where when the very last element in my form has a value bound to it the error "Expression has changed after it was checked." is thrown.
I will preface by saying this is based off of the Angular 2 website example here -
https://angular.io/docs/ts/latest/cookbook/dynamic-form.html#!#top
The way my app works is first I build a dynamic form with controls in my form component based off a model.
My form components html loops the questions in the model like so
<form *ngIf="showForm" [formGroup]="formGroup">
<!-- questions-->
<div *ngIf="questions.length > 0">
<div *ngFor="let question of questions">
<question [question]="question" [formGroup]="formGroup"></question>
</div>
</div>
<button pButton type="submit" label="Submit" icon="fa-check-circle-o" iconPos="left"
[disabled]="!formGroup.valid" (click)="submitFinalForm()"></button>
</form>
Below is the question component html that uses the data that was passed in from the form component to display certain types of questions via ngSwitch
<label [attr.for]="question.field">
{{ question.question }}
</label>
<div [ngSwitch]="question.type">
<!-- Radio / Checkbox -->
<radio-checkbox-question *ngSwitchCase="1" [formGroup]="formGroup" [question]="question"></radio-checkbox-question>
</div>
Finally here is the radio-checkbox-question component
<div *ngIf="showQuestion" [formGroup]="formGroup">
<!-- Radio -->
<div *ngIf="model.radiocheckbox == 'radio'">
<div *ngFor="let label of model.labels; let i = index;">
<p-radioButton name="{{model.field}}"
value="{{i}}"
label="{{label}}"
formControlName="{{model.field}}"
[(ngModel)]="questionAnswerRadio"></p-radioButton>
</div>
</div>
</div>
Here is the actual component TS
import { Component, Input, OnInit } from "#angular/core";
import { FormGroup } from "#angular/forms";
import { RadioCheckboxQuestion } from "../Questions/radio.checkbox.question.model";
#Component({
selector: "radio-checkbox-question",
templateUrl: "radio.checkbox.component.html"
})
export class RadioCheckboxComponent implements OnInit {
#Input() question: any;
#Input() formGroup: FormGroup;
model: RadioCheckboxQuestion = new RadioCheckboxQuestion();
showQuestion: boolean = false;
questionAnswerRadio: string = "";
ngOnInit(): void {
// question essential properties
if (this.question.hasOwnProperty("field") && this.question["field"] &&
this.question.hasOwnProperty("labels") && this.question["labels"]) {
this.model.field = this.question["field"];
this.model.labels = this.question["labels"];
// assume always radio for debugging
this.model.radiocheckbox = "radio";
// set existing answer
if (this.question.hasOwnProperty("QuestionAnswer") && this.question["QuestionAnswer"]) {
if (this.model.radiocheckbox == "radio") {
this.questionAnswerRadio = this.question["QuestionAnswer"];
}
}
this.showQuestion = true;
}
}
}
I've also seen many SO issues like the following
Angular 2 dynamic forms example with ngmodel results in "expression has changed after it was checked" which basically state that [(ngModel)] should not be used with dynamic forms, but the primeNG documentation says the components can work with model driven forms and the only way to set the answer (that I know of) is [(ngModel)]. I believe what might happen here is since I set the only question in the formGroup to a value that the formGroup becomes valid in between the change detection and causes the error
Error in ./FormComponent class FormComponent - inline template:17:48 caused by: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.
From your template it looks like you are using both model drive (formControlName)
and template driven (ngModel).
<p-radioButton name="{{model.field}}"
value="{{i}}"
label="{{label}}"
formControlName="{{model.field}}"
[(ngModel)]="questionAnswerRadio"></p-
<radioButton>
Please select one way and try again.
I suggest you to remove the [(ngModel)]
The only way i've found to get the change detection to be happy with my multi-nested components and primeNG was to implement full change detection manually. What that basically means was in every component I had to add something like the following
import ChangeDetectorRef
constructor(private change: ChangeDetectorRef)
{}
ngOnInit() {
// code here that inits everything
this.change.markForCheck();
}
Anything less then this caused the change detection errors to pop-up in different and unique ways in the components that used primeNG.

Find the hidden checkboxes in Coypu

I'm try to create test for my Bootstrap project. I'm use Coypu. But I ran into a some problem. I can't check my check-boxes. The problem is that I changed style form my check-box. And now standard Bootstraps check-boxes is hidden. The new check-box is hidden inside standard pattern:
<label>
<input type="checkbox" data-bind="checkedValue: key, checked: $parent.selectedCatchments, attr: { id: key }" class="catchment-checkbox" />
<span data-bind=" text: value, attr: { for: key }" class="lbl padding-8 openSans-Text catchment-checkbox-span"></span>
</label>
The problem is that Coypu can't to find the hidden element on browser. And now I can't to check selected check-box or not.
This is standard check-box:
I turned off: opacity: 0 in CSS style.
And this is new checkbox with the new style.
How can I check the number of checked items in Coypu?
I can add ConsideringInvisibleElements = true inside SetUp method, but this option will be works always for all Tests. How I can change value of ConsideringInvisibleElements option on true or false when I need inside test code?
I'm find this variant:
var catchmentsCheckboxes = Browser.FindAllXPath("id('catchmentsColumn')/div[1]/div/label/input", null, new Coypu.Options { ConsiderInvisibleElements = true });
The first parameter: xPath to element;
The second parameter is can be null;
The third parameter is ConsideringInvisibleElements. And we can change value of this parameter on true or false.

Angularjs check if section in the form is valid

I want to check my angular form validity with a little tweak,
I have a form builded dynamically with directives involved, Now the form has more than one page to it, so i play with ng-hide/ng-show when i move from page to page...
All i want to do is to check the validity of the first chunk of form inputs, for example:
I have 3 pages, 3 questions in every 'page', before the user can go to the next page, it should check for validation on the three inputs, and only than! he can move to the next one...
on my form tag i have 'novalidate' so i must do all the validations myself...
What you're after is ng-form
You can't nest HTML <form></form> tags but you can with ng-form to split your form into sections.
i.e.
<form name="myForm">
<ng-form name="subForm1">
<input name="txtField1" type="text" ng-model="Field1" ng-maxlength="50" required>
</ng-form>
<ng-form name="subForm2">
<input name="txtField2" type="text" ng-model="Field2" ng-maxlength="10" required>
</ng-form>
<button type="button1" ng-disabled="subForm1.$invalid" ng-click="doSomething() ">Button 1</button>
<button type="button1" ng-disabled="subForm2.$invalid" ng-click="doSomething()" >Button 2</button>
<button type="button3" ng-disabled="myForm.$invalid" ng-click="doSomething()" >Button 3</button>
</form>
In this instance button1 and button2 are disabled on parts of the form where as button3 is disabled based on the whole forms input
Source: https://docs.angularjs.org/api/ng/directive/ngForm
You can use the Angular's form element property $dirty, or you could check if the element you want to validate has the class ng-dirty.
If you'd like, read more here, it explains how to use and check this.
Angular JS has some pretty features which you can take advantage of especially the class .ng-valid and .ng-invalid. As the user fills your form, angular dose a real time update on the state of form fields by changing the classList to correspond to the current state of the form.
Any for field that is has been altered and does not pass the Angular validation will have a class .ng-invalid well all classes that passed the validation will have .ng-valid. While ng-pristine indicates that the form have not been modified ng-dirty tells you that the form has been modified. Not that ng-pristine and ng-dirty cannot be used to ascertain the validity of the field.
Meanwhile for your case I have created a CodePen
angular.module("paged", [])
.controller("FormCtrl", function($scope) {
$scope.form = {page: 1};
$scope.canShow = function(i) {
return (i === $scope.form.page);
};
$scope.submit = function(form) {
alert("Form Submitted", form);
};
$scope.gotoPage = function(pageTo) {
var show = false;
var els = document.getElementsByTagName("input"); //Just with input only to keep it simple
for (var i = 0; i < els.length; i++) {
if (els[i].hasAttribute("data-page") && els[i].getAttribute("data-page") == pageTo - 1) {
if (els[i].classList.contains("ng-invalid")) {
show = false;
break;
}
}
show = true;
}
if (show) {
$scope.form.page = pageTo;
if (pageTo == 4) {
$scope.submit($scope.form);
}
}
}
});
to show you how this can done. As someone will rightfully say, there may ways to kill a rat. I think this is one of them

Issues getting validation working with dynamically generating form inputs?

I have some basic form/input html that works (including validation) if explicitly written as follows:
<form name="forms.create" novalidate>
<div class="si-container">
<div class="si-input-container">
<input class="si-input" name="someNum" placeholder="Enter a number" ng-model="formdata.number" type="number" min="40"/>
</div>
<div class="si-error">
<div ng-show="forms.create.someNum.$error.min">Error! Value must be > 40.</div>
</div>
</div>
</form>
Now what I want to do is create a directive that allows me to write the html below, but result in the html above:
<form name="forms.create" novalidate>
<div special-input name="someNum" placeholder="Enter a number" type="number" ng-model="formdata.number">
<div error-type="min" error-value="40">Error! Value must be > 40.</div>
</div>
</form>
My attempt at the special-input directive (simplified) is as follows:
.directive('specialInput', [function(){
return {
compile: function(elem, attrs){
var input = angular.element('<input class="si-input"/>');
input.attr('placeholder', attrs.placeholder);
input.attr('type', attrs.type);
input.attr('name', attrs.name);
input.attr('ng-model', attrs.ngModel);
var errorCont = angular.element('<div class="si-error"></div>');
var errors = elem.children();
angular.forEach(errors, function(error){
var err = angular.element(error);
var type = err.attr('error-type');
var value = err.attr('error-value');
input.attr(type, value);
var formName = elem.parent().attr('name');
errorCont.append('<div ng-show="' + formName + '.' + attrs.name + '.$error.' + type + '">' + err.html() + '</div>');
});
var cont = angular.element('<div class="si-container"></div>');
cont.append('<div class="si-floating-label">' + attrs.placeholder + '</div>');
cont.append('<div class="si-input-container">' + input[0].outerHTML + '</div>');
cont.append('<div class="si-underline"></div>');
cont.append(errorCont);
elem.replaceWith(cont[0].outerHTML);
}
};
}]);
Now the resultant html using the directive above looks about right. If I put {{formdata.number}} below the form the value changes as expected. The problem is that now the validation never shows.
For example, if I put the value 5 in the input and inspect the form object, I get weird results. $dirty is set to true for form, but not for form.someNum. If I put 55 in the input, $dirty is still set to false for form.someNum, but $modelValue and $viewValue both show 55.
Any ideas or suggestions? Here is a fiddle to help with any testing.
If you put 50 in the input box you should see the value below, but put 5 and the error does not appear
UPDATE
I have managed to get it working by moving the dom changes into the link function instead of the compile function, and adding this:
elem.replaceWith(cont);
$compile(cont)(scope);
I am still puzzled though, as to why this works, while altering the dom in the exact same way in the compile function doesn't work. Is anyone able to explain this?
It's because the original ng-model is still get compiled even the original DOM has already been replaced by the new one in your compile function.
The ng-model directive will register itself to a parent form in its postLink function. Due to the fact that the postLink function will be executed in reverse (child's before parent's), the new ng-model will do the registration first, thus it will be overridden by the one from the original ng-model eventually.
To avoid this problem, you could change the original ng-model to another name such as my-model, then rename it to ng-model later in your compile function.
Example jsfiddle: http://jsfiddle.net/Wr3cJ/1/
Hope this helps.

JQuery UI Slider Without Numbers

I am trying to build a rating form and want to use the JQuery UI increment slider except I don't want the value to be numbers, I'd like it to say something like "great" or "bad"
I'm great with HTML & CSS not so much with JavaScript or JQuery. I am taking a class in NYC next month to help me become more fluent in these languages. In the mean time any and all help is appreciated. I tried to hack other scripts I have found but keep running into problems. I ran into this script which is close to what I want, except the increments show #s.
I feel as if this code can be easily modified to do what I want.
http://jsfiddle.net/dmcgrew/EquTn/3/
<html>
<div class="kpa_rate kpa_rate1">
<label for="kpa1_rating_value">Parking:</label>
<div id="1" class="slider"></div>
<input type="text" class="kpa1_rating_value" name="kpa1_rating" value="0" />
</div>
<div class="kpa_rate kpa_rate2">
<label for="kpa2_rating_value">Entrance:</label>
<div id="2" class="slider"></div>
<input type="text" class="kpa2_rating_value" name="kpa2_rating" value="0" />
</div>
</html>
JavaScript
<script>
$(function() {
$( ".slider" ).slider({
range: "max",
min: 0,
max: 5,
value: $("input", this).val(),
slide: function( event, ui ) {
//get the id of this slider
var id = $(this).attr("id");
//select the input box that has the same id as the slider within it and set it's value to the current slider value.
$("span[class*=" + id + "]").text(ui.value);
$("input[class*=" + id + "]").val(ui.value);
}
});
});
</script>
Did you want different words ("great, good, neutral, etc") for each value, or just good/bad at the ends?
If you want diff words for each of the 5 values, then like j08691 said, make an array that has the words you want at the indexes you want.
After this, replace the ui.value in .val(ui.value) with the array of value (Arr[ui.value]).
(I'm not sure you actually need the "span" part, but I don't do jquery, so I'm not sure).
$(function() {
var Array = ["die","very bad", "bad", "neutral","good","great"];
$( ".slider" )...etc
...
$("input[class*=" + id + "]").val(Array[ui.value]);
This worked for me.
I did a bit of tinkering, and I think I've found a way to superficially solve your problem. Since the number inputted as the first value doesn't affect the slider button/thing, I'd say just put the first word that the slider should indicate (in my case, "die").
//value does not equal a number here
In the next bit of code, just change input to Array[input], and you should be all set!
min: 0,
max: 5,
value: $("Array[input]"
I hope this works well enough for you.