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

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.

Related

What is the best way to post form with multiple components using Vue js

as I'm on my Vue spree (started recently but so far I'm really enjoying learning this framework) couple of questions rised up. One of which is how to post form from multiple components. So before I continue forward I wanted to ask you what are you thinking about this way of structuring and point me in right direction if I'm wrong.
Here it goes.
I'm working on a SPA project using ASP.NET CORE 2.1 and Vue JS Template (with webpack)(https://github.com/MarkPieszak/aspnetcore-Vue-starter) and my project is structured in several containers, something like this:
In my app-root i registered several containers
<template>
<div id="app" class="container">
<app-first-container></app-first-container>
<app-second-container></app-second-container>
<!--<app-third-container></app-third-container>-->
<app-calculate-container></app-calculate-container>
<app-result-container></app-result-container>
</div>
</template>
<script>
// imported templates
import firstContainer from './first-container'
import secondContainer from './second-container'
import calculateContainer from './calculateButton-container'
//import thirdContainer from './third-container'
import resultContainer from './result-container'
export default {
components: {
'app-first-container': firstContainer,
'app-second-container': secondContainer,
// 'app-third-container': thirdContainer,
'app-calculate-container': calculateContainer,
'app-result-container': resultContainer
}
}
</script>
In my first container I'm having several dropdowns and two input fields with my script file where I'm fetching data from API and filling dropdowns and input fields with fetched data.
Something like this ( entered some dummy code for demonstration)
<template>
<div>
<h1>Crops table</h1>
<p>This component demonstrates fetching data from the server. {{dataMessage}}</p>
<div class="form-row">
<div class="form-group col-md-6">
<label for="exampleFormControlSelect1" class="col-form-label-sm font-weight-bold">1. Some text</label>
<select class="form-control" id="exampleFormControlSelect1" v-model="pickedCropType" #change="getCropsByType()">
<option v-for="(cropType, index) in cropTypes" :key="index" :value="cropType.id" :data-imagesrc="cropType.imgPath">{{ cropType.name }}</option>
</select>
</div>
<div class="form-group col-md-6">
<label for="exampleFormControlSelect2" class="col-form-label-sm font-weight-bold">2. Some text</label>
<select class="form-control" id="exampleFormControlSelect2">
<option v-for="(crop, index) in cropSelectList" :key="index" :value="crop.id">{{ crop.name }}</option>
</select>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex'
export default {
data() {
return {
cropTypes: null,
cropSelectList: null,
crops: null,
pickedCropType: null,
}
},
methods: {
loadPage: async function () {
try {
//Get crop types and create a new array with crop types with an added imgPath property
var cropTypesFinal = [];
let responseCropTypes = await this.$http.get(`http://localhost:8006/api/someData`);
responseCropTypes.data.data.forEach(function (element) {
cropTypesFinal.push(tmpType);
});
} catch (err) {
window.alert(err)
console.log(err)
}
},
getCropsByType: async function () {
//Get crops by crop type
let responseCrops = await this.$http.get(`http://localhost:8006/api/crop/Type/${this.pickedCropType}`);
var responseCropsData = responseCrops.data.data;
this.cropSelectList = responseCropsData;
}
},
async created() {
this.loadPage()
}
}
</script>
And in my second container I have different dropdowns and different input fields with different scripts etc.
So, my questions are:
1.) I'm having required data form field in first container and in second container I'm having additional data and my submit button is separated in third container (app-result-container). So, is this proper and logical way of structuring containers if not can you point me in right direction?
2.) Is it smart to input script tag in every container where I'm processing/fetching/submitting some data for that particular container? Should I put scripts tag in separated file and keep structure clean, separating html from js file.
Example:
import { something } from 'something'
export default {
data () {
return {
someData: 'Hello'
}
},
methods: {
consoleLogData: function (event) {
Console.log(this.someData)
}
}
}
3.) Can I send input values from one container to another (In my particular case from first and second container to app-calculate-container(third container))?
How to on submit return results container with calculated imported values
If you want components to communicate or share data with one another, you will need to either emit an event from one component up to the parent and pass it down via props, or use some kind of state management model, like Vuex, where each of your components can listen to the store.
Take a look at this code sandbox: https://codesandbox.io/s/8144oy7xy2
App.vue
<template>
<div id="app">
<child-input #input="updateName" />
<child-output :value="name" />
</div>
</template>
<script>
import ChildInput from "#/components/ChildInput.vue";
import ChildOutput from "#/components/ChildOutput.vue";
export default {
name: "App",
components: {
ChildInput,
ChildOutput
},
data() {
return {
name: ""
};
},
methods: {
updateName(e) {
this.name = e.target.value;
}
}
};
</script>
ChildInput.vue
<template>
<input type="text" #input="changeHandler">
</template>
<script>
export default {
name: "ChildInput",
methods: {
changeHandler(e) {
this.$emit("input", e);
}
}
};
</script>
ChildOutput.vue
<template>
<p>{{ value }}</p>
</template>
<script>
export default {
name: "ChildOutput",
props: {
value: {
type: String,
default: ""
}
}
};
</script>
What's going on?
The ChildInput component is a text field and on every change inside it, fires an event (emits using this.$emit() and passes the whole event up).
When this fires, App is listening to the change, which fires a method that updates the name data property.
Because name is a reactive data property and is being passed down as a prop to the ChildOutput component, the screen re-renders and is updated with the text written.
Neither ChildInput nor ChildOutput knows about one another. It's the parent that listens to the event passed to it, then passes the new prop down.
This way of working is fine and simple to understand, but I would strongly recommend looking at Vuex, as this method can get messy and complicated when you go beyond trivial tasks.

Angular 2 Error: Cannot find control with unspecified name attribute - Components inside components

I have to create a form , And I want to be more clear about components
The main idea of form is:
FormComponent
|>
FieldComponent
|> InputComponent of Form
So I Have PartnerFormComponent:
Html:
<form [formGroup]="partnerForm" (ngSubmit)="save()">
<!--<legal-information
[(partner)]="partner" [(partnerConfiguration)]="partnerConfiguration" ></legal-information>-->
<combo-billing-entity [(formGroup)]="partnerForm" [(partner)]="partner" [(partnerConfiguration)]="partnerConfiguration"></combo-billing-entity>
<div class="buttons_form">
<button class="save_button_form" type="submit" [disabled]="!partnerForm.valid">
Add
</button>
<a class="btn-floating btn-large waves-effect waves-light green"
routerLink="/partners">
<i class="material-icons">Cancel</i>
</a>
</div>
</form>
And ts:
#Component({
selector: 'partner-form',
templateUrl: './partner-form.component.html',
styleUrls: ['./partner-form.component.css'],
entryComponents:[LegalInformationComponent]
})
export class PartnerFormComponent implements OnInit {
private partnerForm: FormGroup;
title: string;
partner: Partner = new Partner();
partnerConfiguration: PartnerConfiguration = new PartnerConfiguration();
constructor(
private router: Router,
private route: ActivatedRoute,
private partnerService: PartnerService
) { }
ngOnInit() {
var id = this.route.params.subscribe(params => {
var id = params['id'];
if (!id)
return;
.....
});
}
then from component i have html Combo:
<div class="components_situation">
<div class="field_form_title">
{{title}} <span class="is_required_form" [hidden]="!isRequired">*</span>
</div>
<div [formGroup]="formGroup" >
<select id="billingEntity" [(ngModel)]="partnerConfiguration.fakeBillingEntity"
formControlName="billingEntity"
[class.invalid]="form.controls['billingEntity'].touched && !form.controls['billingEntity'].valid"
>
<option disabled hidden [value]="selectUndefinedOptionValue">-- select --</option>
<option *ngFor="let obj of billingEntities" [value]="obj.value" >{{obj.name}}</option>
</select>
</div>
<div class="some_explanation_form_field">{{someExplanation}}</div>
</div>
And TS:
import {Component, Input, OnInit} from "#angular/core";
import {CommonFieldFormComponent} from "../../common-field-form-component";
import {BillingService} from "../../../../../../services/billing/billing.service";
import {BillingEntitity} from "../../../../../../model/billing_entity";
import {FormBuilder, FormGroup, Validators} from "#angular/forms";
#Component({
selector: 'combo-billing-entity',
templateUrl: './combo-billing-entity.component.html'
})
export class ComboBillingEntityComponent extends CommonFieldFormComponent implements OnInit {
private selectUndefinedOptionValue:any;
billingEntities:BillingEntitity[] = [];
#Input()
private formGroup:FormGroup;
constructor(private billingService: BillingService, private formBuilder:FormBuilder)
{
super();
this.isRequired=true;
this.title="Billing Entity";
this.someExplanation="Identifies entity responsible for billing invoice";
this.formGroup = this.formBuilder.group({
billingEntity :['', Validators.required]
});
}
But after all I have this error:
ComboBillingEntityComponent.html:5 ERROR Error: Cannot find control with unspecified name attribute
at _throwError (forms.es5.js:1852)
at setUpControl (forms.es5.js:1760)
at FormGroupDirective.webpackJsonp.../../../forms/#angular/forms.es5.js.FormGroupDirective.addControl (forms.es5.js:4733)
at FormControlName.webpackJsonp.../../../forms/#angular/forms.es5.js.FormControlName._setUpControl (forms.es5.js:5321)
at FormControlName.webpackJsonp.../../../forms/#angular/forms.es5.js.FormControlName.ngOnChanges (forms.es5.js:5239)
at checkAndUpdateDirectiveInline (core.es5.js:10831)
at checkAndUpdateNodeInline (core.es5.js:12330)
at checkAndUpdateNode (core.es5.js:12269)
at debugCheckAndUpdateNode (core.es5.js:13130)
at debugCheckDirectivesFn (core.es5.js:13071)
Any Idea how to bind inputs to main form.. what I'm doing wrong ?
You have quite a few issues in your form. You are trying to use two-way-binding in your Input fields, for example [(formGroup)]="partnerForm" But then you are nowhere using #Output to actually trigger the two-way-binding, so you are not using it properly.
The form object is indeed an object, and objects are mutable in JS and passed by reference. Sometimes this is not desirable behavior, but in this scenario we want that, where we have nested components. So whatever you do to form fields in child component, parent will be aware, so you don't actually need the two-way-binding.
Secondly, please avoid using [(ngModel)] in reactive forms. I've noticed that weird behavior can occur, which is understandable, since we then have two bindings, to the ngModel variable and the other is the form control variable. Utilize the form controls instead, and remove all ngModel from your templates. You can set values to the form control, that will basically function as two-way-binding as you can access the form control value from TS as well any time you like. So [(ngModel)]
Build your whole form in parent and then pass the formgroup over to the child, or alernatively pass a nested formgroup to your child. So here in your parent you actually want to build the form:
this.partnerForm = this.fb.group({
billingEntity: ['hey I am initial value']
})
Above you cans set initial value to billingEntity, or if you need at some other point manually set a default value you can do that by: this.partnerForm.get('billingEntity').setValue(...)
We now pass this form to the child:
<combo-billing-entity [partnerForm]="partnerForm"></combo-billing-entity>
and in child we register it as an Input:
#Input() formGroup: FormGroup;
Then we can then just use it, e.g:
<div [formGroup]="partnerForm">
<input formControlName="billingEntity" />
</div>
I see that you are trying to use [(ngModel)] there, but as mentioned, you can drop that and use the form control instead. The value is stored nicely in formGroup.get('billingEntity').value and as earlier mentioned, if you need to set the value at some point you can do that. But all your form values are nicely stored in the form object, i.e partnerForm.value.
Here's a simple demo: http://plnkr.co/edit/AuidEMaaURsBPfDP8k0Q?p=preview
I suggest you read about nested forms, this one is pretty good to get started with: Nested Model-driven Forms
Your component will have to implement ControlValueAccessor and register it is a multi dependency. Pascal Precht has a very good blog post on custom form controls in Angular.
In short words, after you implement ControlValueAccessor by defining functions writeValue, registerOnChange and registerOnTouched, you'll have to provide the existing value so Angular can know that you're trying to use it a form control.
#Component({
// ...
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ComboBillingEntityComponent),
multi: true,
}
],
// ...
})
class ComboBillingEntityComponent implements ControlValueAccessor {
// ...
}
After this, you can use formControlName on <combo-billing-entity> tag in your templates:
<combo-billing-entity formControlName="billingInfo"></combo-billing-entity>

What causes the "control.registerOnChange is not a function" error

I have a form using the reactive form approach. The form is created as follow in my pug:
form([formGroup]='form', novalidate='', (ngSubmit)='postSurvey(form.value, form.valid)')
Everything works fine except when I try to change the form (which is a FormArray) in the javascript part. I get the following error:
EXCEPTION: Error in http://localhost:8080/app/components/fillForm.template.html:0:326 caused by: control.registerOnChange is not a function
core.umd.js:3497 TypeError: control.registerOnChange is not a function
at setUpControl (http://localhost:8080/node_modules/#angular/forms/bundles/forms.umd.js:1634:17)
at eval (http://localhost:8080/node_modules/#angular/forms/bundles/forms.umd.js:4752:25)
at Array.forEach (native)
at FormGroupDirective._updateDomValue (http://localhost:8080/node_modules/#angular/forms/bundles/forms.umd.js:4747:29)
at FormGroupDirective.ngOnChanges (http://localhost:8080/node_modules/#angular/forms/bundles/forms.umd.js:4616:22)
at Wrapper_FormGroupDirective.ngDoCheck (/ReactiveFormsModule/FormGroupDirective/wrapper.ngfactory.js:30:18)
at View_FillFormComponent2.detectChangesInternal (/AppModule/FillFormComponent/component.ngfactory.js:275:32)
at View_FillFormComponent2.AppView.detectChanges (http://localhost:8080/node_modules/#angular/core/bundles/core.umd.js:12592:18)
at View_FillFormComponent2.DebugAppView.detectChanges (http://localhost:8080/node_modules/#angular/core/bundles/core.umd.js:12739:48)
at ViewContainer.detectChangesInNestedViews (http://localhost:8080/node_modules/#angular/core/bundles/core.umd.js:12850:41)
at CompiledTemplate.proxyViewClass.View_FillFormComponent0.detectChangesInternal (/AppModule/FillFormComponent/component.ngfactory.js:64:14)
at CompiledTemplate.proxyViewClass.AppView.detectChanges (http://localhost:8080/node_modules/#angular/core/bundles/core.umd.js:12592:18)
at CompiledTemplate.proxyViewClass.DebugAppView.detectChanges (http://localhost:8080/node_modules/#angular/core/bundles/core.umd.js:12739:48)
at CompiledTemplate.proxyViewClass.AppView.internalDetectChanges (http://localhost:8080/node_modules/#angular/core/bundles/core.umd.js:12577:22)
at CompiledTemplate.proxyViewClass.View_FillFormComponent_Host0.detectChangesInternal (/AppModule/FillFormComponent/host.ngfactory.js:29:19)
My code to change the form is quite complex and I can't simplify it or reproduce it in a plunker. More than finding directly the solution (it's too difficult with so little details), I would like to understand what this error means? And what might cause this error.
I have figured out that the error occurs at [formGroup]='form' in my HTML.
Any suggestion will help.
Update I have filed an issue on angular github here and have proposed a fix here The plunker to reproduce the issue is here
Yes, that error message is a bit cryptic, but if you use FormBuilder, you would see this when you added a control to FormGroup in your component and named it "A", but then either forgot to add input with formControlName="A" to your template, or formControlName for the intended input is not A, or empty, or not present.
Basically, it says: "I cannot match the control I have in FormGroup to the control in the template".
I came across looking for a solution to the similar issue and then found a solution myself.
My issue was the following. I had a form like this
form: FormGroup = new FormGroup({
status: new FormArray([])
});
Initially it was represented by the list of checkboxes for each status on the template. And then I created a custom component to represent status selector and used it in template like so
<status-selector [formControlName]="'status'"></status-selector>
The problem is that formControlName must point to FormControl instance, but actually it was pointing to a FormArray instance. So, changing to status: new FormControl([]) fixed this issue for me.
In my case this error was thrown because I was using FormControlName instead of FormArrayName to bind to a FormArray in my template.
My component code:
public form = new FormGroup({
...
checkboxes: new FormArray([....])
})
My template code that threw error:
<input type="checkbox" formControlName="checkboxes" />
Fix:
<input type="checkbox" formArrayName="checkboxes" />
I have also encountered this error when mixing template driven with reactive driven approaches (by mistake):
<input #inputCtrl
[formControl]="inputCtrl"
/>
inputCtrl was properly defined in the component. Of course, #inputCtrl must be scrapped in order to work (it was hard to see when input had about 10 attributes).
In my case the error occurred when the formControl name was same as a template variable on the page. For example
<select id="status" [formControl]="userStatus">...</select>
<form-status #userStatus ></form-status> //matching template variable name
this.extensionForm = this._fb.group({
id: [''],
category: [12],
extensions: new FormArray([]),
priority: ['', []],
});
formArray.push(this.extensionForm);
Note :- error occurs because of you have used formControlName
where you declare formArray, you must use formArrayName instead
<input type="text" formControlName="extensions" />
simple solution:
<input type="checkbox" formArrayName="extensions" />
If have defined a FormArray field in your form, note that you do NOT need to label it with formControlName="". You need to handle the input and validation in other ways (setters, getters, functions), but will definitely get an error if you try to assign formControlName to a FormArray!
This error also appears when we use a reactive form inside ng-template in conjunction with *ngIf.
To avoid this use ng-container and do not use ngElse.
In my case I got the error when I used formControlName in the template when actual form model was a FormGroup instance. Changing to formControlGroup helped.
Maybe you have moved a control element outside the group in the template.
OK:
<div formGroupName="passwordForm">
Password: <input type="password" formControlName="password">
Confirm: <input type="password" formControlName="confirmPassword">
</div>
Not OK:
Password: <input type="password" formControlName="password">
<div formGroupName="passwordForm">
Confirm: <input type="password" formControlName="confirmPassword">
</div>
In my case the issue was that I was referring to something as a FormGroup instead of a FormControl with an object as the value.
This was my initial, faulty code:
formArray.push(
new FormGroup({
value: new FormControl('', Validators.required),
description: new FormControl('', Validators.required),
tags: new FormArray([], Validators.minLength(1)),
}),
);
and in the template, I was using a component (custom-form-component in this example) which implements ControlValueAccessor:
<li *ngFor="let item of items; let itemIndex = index"
<custom-form-component [formGroupName]="itemIndex"></custom-form-component>
</li>
This group is handled in the custom-form-component component which I'm using, therefore instead a FormControl should be used:
formArray.push(
new FormControl({
value: '',
description: '',
tags: [],
}),
);
and in the template, use formControlName instead of formGroupName:
<li *ngFor="let item of items; let itemIndex = index"
<custom-form-component [formControlName]="itemIndex"></custom-form-component>
</li>
In Angular I had the same problem when I tried to build my own form component and forgot to implement the ControlValueAccessor interface:
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'my-country-select',
templateUrl: './country-select.component.html',
styleUrls: ['./country-select.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CountrySelectComponent),
multi: true,
},
],
})
export class CountrySelectComponent implements OnInit, OnChanges, ControlValueAccessor {
propagateChange = (_: any) => { }; // ControlValueAccessor
private internalValue: string | undefined;
get value(): string | undefined {
return this.internalValue;
}
set value(value: string | undefined) {
this.internalValue = value;
this.propagateChange(value);
}
// some other methods here
// implementing the ControlValueAccessor interface with these three methods
writeValue(obj: any): void {
if (obj) {
this.value = obj;
}
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
// throw new Error('Method not implemented.');
}
}
I encountered this when I accidentally reset the value of my form control property instead of using the .setValue() method.
Wrong
control = new FormControl([1])... control = []
Right
control = new FormControl([1])... control.setValue([])
Adding in what was causing it in my situation.
.ts file,
...
export class MyComponent {
dateFrom = new FormControl(new Date());
...
}
.html file,
<mat-form-field>
<input matInput [matDatepicker]="dateFrom" [formControl]="dateFrom"
placeholder="Min date">
<mat-datepicker-toggle matSuffix [for]="dateFrom"></mat-datepicker-toggle>
<mat-datepicker #dateFrom ></mat-datepicker>
</mat-form-field>
Basically, within the template file, it didn't know which 'dateFrom' to choose, and chooses the one in the template file, which isn't the FormControl in my typescript file, it's the mat-datepicker element in the template file.
Renaming the FormControl to dateFromCtrl fixed this,
i.e.
...
export class MyComponent {
dateFromCtrl = new FormControl(new Date());
...
}
.html file,
<mat-form-field>
<input matInput [matDatepicker]="dateFrom" [formControl]="dateFromCtrl"
placeholder="Min date">
<mat-datepicker-toggle matSuffix [for]="dateFrom"></mat-datepicker-toggle>
<mat-datepicker #dateFrom ></mat-datepicker>
</mat-form-field>
Works as expected.
Kodos to VS Code for figuring this out. I got pushed this direction by doing a Cmd + Click on the initial 'dateFrom' at [formControl]="dateFrom", and it pointed me to the mat-datepicker element.
to me it happened when I used same [formControl]="carBrand" and [matAutocomplete]="carBrandAuto" from my autocomplete input
I changed this
FROM:
...
<input
[formControl]="carBrand"
[matAutocomplete]="carBrand"
>
<mat-autocomplete matAutocomplete #carBrand="matAutocomplete">
...
TO
...
<input
[formControl]="carBrand"
[matAutocomplete]="carBrandAuto"
>
<mat-autocomplete matAutocomplete #carBrandAuto="matAutocomplete">
...
For future readers, my problem was a simple one and it causes TypeError: control.registerOnChange is not a function
When I was creating the new FormGroup I accidentally used FormGroup instead of FormControl
myForm: FormGroup = new FormGroup({
lname: new FormGroup({}),
fnam: new FormGroup({}),
date: new FormGroup({}),
});
It should have been this:
myForm: FormGroup = new FormGroup({
lname: new FormControl(''),
fnam: new FormControl(''),
date: new FormControl(null),
});
Hopefully this will save someone a few minutes in the future!
I had this error when I tried to access formGroup control like this
myFormGroup['controlName']
instead of using .get('controlName'). and btw If .get('...') is causing some typing problems, see this: https://stackoverflow.com/a/67835904/8094012
In my case, I was passing simple property to formControl i.e.
tagsList: Array<string> = ['banana', 'apple']
<myComponent [formControl]="tagsList"></myComponent>
but generally it should be like
tagsList = new FormControl()
<myComponent [formControl]="tagsList"></myComponent>
or
tagsList: Array<string> = ['banana', 'apple']
<myComponent [(ngModel)]="tagsList"></myComponent>

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

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.

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