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>
I need a selector for this code:
<div class="panel-heading BWHeadingForSection">
<a style="text-decoration:none" href="#faq-cat-1-sub-2" data-parent="#accordion-cat-2" data-toggle="collapse" tabindex="90">
<i class="fa fa-truck fa-lg"></i>
UPS Deutschland
<span class="pull-right">
<i class="glyphicon glyphicon-plus"></i>
</span>
</a>
</div>
And I tried that:
$('div.BWHeadingForSection a[href~="#faq-cat-1-sub-'+globalcarriernumber+'"]').on("click", function(){
count++;
if(count%2==0){
$("#faq-cat-1-sub-"+globalcarriernumber).slideToggle();
//why doesn't this selector work?
//if ( $(this+">span i").hasClass('plus') ) {
// $(this+">span i").removeClass('plus').addClass('minus');
// }
//this selector work's, at least it enters the if -block
if ( $('div.BWHeadingForSection a[href~="#faq-cat-1-sub-'+globalcarriernumber+'"]>span i').hasClass('plus') ) {
$('div.BWHeadingForSection a[href~="#faq-cat-1-sub-'+globalcarriernumber+'"]>span i').removeClass('glyphicon glyphicon-plus').addClass('glyphicon glyphicon-minus');
}
}else{
$("#faq-cat-1-sub-"+globalcarriernumber).hide();
}
});
The relevant line is "$(this+">span i")" and/or "$('div.BWHeadingForSection a[href~="#faq-cat-1-sub-'+globalcarriernumber+'"]>span i')" , which needs some adjustment. I have to replace that "plus"("") with "minus".
If I use the FireBug debugger I get for "$('div.BWHeadingForSection a[href~="#faq-cat-1-sub-'+globalcarriernumber+'"]>span i')" ,it goes into that if-block, but the symbol is still the same. And if I use "$(this+">span i")" I get(when I hove over it) : "a#faq-cat-1-sub-2",but it's not entering the if-block.
Thanks...
First of all you are mixing types here. The function $(...) accepts either an object (a DOM node or a jQuery object) or a selector string but not a concatenation of both.
So better try $(this).find('span i').
As far as I can see in your code snippet, the element you will find then, has the css classes 'glyphicon' and 'glyphicon-plus', but no class 'plus'. So you probably won't be able to remove that. Maybe you want to remove 'glyphicon-plus' and add 'glyphicon-minus'?
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.
I m using jQuery 1.7+ latest .on method, but failed to implements, please help me.
working Fiddle.
basically here is my
HTML
<ul id="sortByRight" >
<li id="1">List</li>
<li id="2">Photo</li>
<li id="3">Map</li>
</ul>
<select name="sort" id="sort" >
<option value="1">Recommended</option>
<option value="2">Price: low to high</option>
<option value="3">Price: high to low </option>
<option value="4">Newest</option>
</select>
jQuery code
$(document).on('change click', 'ul#sortByRight,select#sort', function() {
selectedOption = $('select#sort').val();
whatToShow = $(this).attr('id');
alert('selectedOption:'+selectedOption+'whatToShow:'+whatToShow);
}
);
now I havebelow problems/queries.
can we bind one event with one selector i.e. above function should be called
EITHER on change of selectbox OR on click of ul.
how to set data argument in .on method. I have tried like below
$(document).on('change click', 'ul#sortByRight,select#sort',
{ selectedOption : $('select#sort').val(), whatToShow : $(this).attr('id') } ,
function(){console.log('selectedOption:'+selectedOption+'whatToShow:'+whatToShow);}
);
but get an error that selectedOption is not defined.
can we write something like this $(this, li); because I need the id of li not the id of selectbox.
if there is any other optimized solution ( using function like live or bind ), then please tell me.
Thanks A Lot.
I'm not 100% clear on what you actually want to do, but one thing doesn't make much sense. You want to send the ID of the LI when the select box is changed. Which LI? The last LI clicked? You need to store the state of the active LI so that you can send it in the ajax request if the select box is changed.
Perhaps something like this:
$('select#sort').change(function() {
processAjax();
});
$('ul#sortByRight > li > a').click(function() {
$(this).closest('ul').find('li').removeClass('active');
$(this).closest('li').addClass('active');
processAjax();
});
function processAjax() {
selectedOption = $('select#sort').val();
whatToShow = $('ul#sortByRight').find('.active').attr('id');
alert('selectedOption:' + selectedOption + 'whatToShow:' + whatToShow);
}
or check out the jsFiddle
I have a method of a class as follows:
add_file: function(name, id, is_new){
// HTML: <div class="icon mime zip">name.zip <a>x</a></div>
var components = name.split('.');
var extension = components[components.length-1];
this.container.innerHTML += "<div id='"+id+"' class='icon mime "+extension+"'>"+name+" <a id='remove-"+id+"' href='#remove'>x</a></div>";
// Add event to a tag
dojo.connect(dojo.byId('remove-'+id), 'onclick', function(ev){
// here i am
});
},
All is working well, until I run this method more than once. The first time the event is registered correctly, and clicking the 'x' will run the "here i am" function. However, once I add more than one node (and yes, the ID is different), the event is registered to the last node, but removed from any previous ones.
In affect I have this:
<div id="field[photos]-filelist">
<div id="file1" class="icon mime jpg">file1.jpg <a id="remove-file1" href="#remove">x</a></div>
<div id="file2" class="icon mime jpg">file2.jpg <a id="remove-file2" href="#remove">x</a></div>
</div>
...and the remove link only works for the last node (remove-file2 in this case).
The problem is you are using the innerHTML +=
That is going to take the existing html, convert it to plain markup, and then completely create new nodes from the markup. In the process, all of the nodes with events get replaced with nodes that look exactly the same but are not connected to anything.
The correct way to do this is to use dojo.place(newNodeOrHTML, refNode, positionString)
var myNewHTML = "<div id='"+id+"' class='icon mime "+extension+"'>"+name+" <a id='remove-"+id+"' href='#remove'>x</a></div>"
//This won't work as is breaks all the connections between nodes and events
this.container.innerHTML += myNewHTML;
//This will work because it uses proper dom manipulation techniques
dojo.place(myNewHTML, this.container, 'last');