What is ngDefaultControl in Angular?
[ngDefaultControl]
Third party controls require a ControlValueAccessor
to function with angular forms. Many of them, like Polymer's <paper-input>
, behave like the <input>
native element and thus can use the DefaultValueAccessor
. Adding an ngDefaultControl
attribute will allow them to use that directive.
<paper-input ngDefaultControl [(ngModel)]="value>
or<paper-input ngDefaultControl formControlName="name">
So this is the main reason why this attribute was introduced.It was called ng-default-control
attribute in alpha versions of angular2.
So ngDefaultControl
is one of selectors for DefaultValueAccessor directive:
@Directive({
selector:
'input:not([type=checkbox])[formControlName],
textarea[formControlName],
input:not([type=checkbox])[formControl],
textarea[formControl],
input:not([type=checkbox])[ngModel],
textarea[ngModel],
[ngDefaultControl]', <------------------------------- this selector
...
})
export class DefaultValueAccessor implements ControlValueAccessor {
What does it mean?It means that we can apply this attribute to element (like polymer component) that doesn't have its own value accessor. So this element will take behaviour from DefaultValueAccessor
and we can use this element with angular forms.
Otherwise you have to provide your own implementation of ControlValueAccessor
ControlValueAccessor
Angular docs states
A ControlValueAccessor acts as a bridge between the Angular forms APILet's write the following template in simple angular2 application:
and a native element in the DOM.
<input type="text" [(ngModel)]="userName">
To understand how our input
above will behave we need to know which directives are applied to this element. Here angular gives out some hint with the error:Unhandled Promise rejection: Template parse errors: Can't bind toOkay, we can open SO and get the answer: import
'ngModel' since it isn't a known property of 'input'.
FormsModule
to your @NgModule
:@NgModule({
imports: [
...,
FormsModule
]
})
export AppModule {}
We imported it and all works as intended. But what's going on under the hood?FormsModule exports for us the following directives:
@NgModule({
...
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}
After some investigation we can discover that three directives will be applied to our input
NgControlStatus
@Directive({
selector: '[formControlName],[ngModel],[formControl]',
...
})
export class NgControlStatus extends AbstractControlStatus {
...
}NgModel
@Directive({
selector: '[ngModel]:not([formControlName]):not([formControl])',
providers: [formControlBinding],
exportAs: 'ngModel'
})
export class NgModel extends NgControl implements OnChanges,DEFAULT_VALUE_ACCESSOR
@Directive({
selector:
`input:not([type=checkbox])[formControlName],
textarea[formControlName],
input:not([type=checkbox])formControl],
textarea[formControl],
input:not([type=checkbox])[ngModel],
textarea[ngModel],[ngDefaultControl]',
,,,
})
export class DefaultValueAccessor implements ControlValueAccessor {
NgControlStatus
directive just manipulates classes like ng-valid
, ng-touched
, ng-dirty
and we can omit it here.DefaultValueAccesstor
provides NG_VALUE_ACCESSOR
token in providers array:export const DEFAULT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultValueAccessor),
multi: true
};
...
@Directive({
...
providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultValueAccessor implements ControlValueAccessor {
NgModel
directive injects in constructor NG_VALUE_ACCESSOR
token that was declared on the same host element.export NgModel extends NgControl implements OnChanges, OnDestroy {
constructor(...
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
In our case NgModel
will inject DefaultValueAccessor
. And now NgModel directive calls shared setUpControl
function:export function setUpControl(control: FormControl, dir: NgControl): void {
if (!control) _throwError(dir, 'Cannot find control with');
if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');
control.validator = Validators.compose([control.validator !, dir.validator]);
control.asyncValidator = Validators.composeAsync([control.asyncValidator !, dir.asyncValidator]);
dir.valueAccessor !.writeValue(control.value);
setUpViewChangePipeline(control, dir);
setUpModelChangePipeline(control, dir);
...
}
function setUpViewChangePipeline(control: FormControl, dir: NgControl): void
{
dir.valueAccessor !.registerOnChange((newValue: any) => {
control._pendingValue = newValue;
control._pendingDirty = true;
if (control.updateOn === 'change') updateControl(control, dir);
});
}
function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
// control -> view
dir.valueAccessor !.writeValue(newValue);
// control -> ngModel
if (emitModelEvent) dir.viewToModelUpdate(newValue);
});
}
And here is the bridge in action:NgModel
sets up control (1) and calls dir.valueAccessor !.registerOnChange
method. ControlValueAccessor
stores callback in onChange
(2) property and fires this callback when input
event happens (3). And finally updateControl
function is called inside callback (4)
function updateControl(control: FormControl, dir: NgControl): void {
dir.viewToModelUpdate(control._pendingValue);
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
}
where angular calls forms API control.setValue
.That's a short version of how it works.
Angular 2 ngDefaultControl
Declraing an input name worked it out. When I used item.amount
the value was 0, changing it to item.name
make it work fine....
Unable to initialize default values when using ngDefaultControl in Angular
It's because [(ngModel)] is an angular directive for native html element
If you want to create a custom input, you need to create an @Input variable inside your el element inside the elElement component and bind it inside your app
@Component({
selector: 'el-input',
template: ` <input type="text" [(ngModel)]='name' /> `,
})
export class ElInput {
@Input()
name: string
}
@Component({
selector: 'app-home',
template: `
<el-input ngDefaultControl [name]="name"></el-input>
<p>{{ name }}</p>
<button (click)="onClick()">Change Name</button>
`,
})
export class HomeComponent {
name: string = 'Tom';
onClick() {
this.name = 'Jack';
}
}
and if you want to change the value of the variable name inside your app-home when the el input is updated, you need to create an @Output variable which will emit the new value for your app-home componentI made a stackblitz which solve your problem here
https://stackblitz.com/edit/angular-utyrxo
No value accessor for form control with name: 'dateTime'
External library require a ControlValueAccessor to work with angular forms.
Try to add ngDefaultControl
like this:
<owl-dateTime-input [(ngModel)]="flightDetails.dateTimeDeparture" name="dateTimeDeparture" [locale]="en" required formControlName="dateTimeDeparture" ngDefaultControl></owl-dateTime-input>
or<owl-dateTime-input [(ngModel)]="flightDetails.dateTimeDeparture" name="dateTimeDeparture" [locale]="en" required ngDefaultControl></owl-dateTime-input>
Look here:What is ngDefaultControl in Angular?
ERROR Error: No value accessor for form control with unspecified name attribute on switch
I fixed this error by adding the name="fieldName" ngDefaultControl
attributes to the element that carries the [(ngModel)]
attribute.
Pass a ngModel from a Child Component to a Parent Component containing the ngForm
You can just add 'ngDefaultControl' to your child where you have the ngModel attribute, and it works fine.
<child [name]="'InputName'" [type]="'text'" ngModel ngDefaultControl></child>
For detailed explanations please refer below:What is ngDefaultControl in Angular?
ERROR Error: No value accessor for form control with unspecified name attribute on switch
How does Angular makes native element connect with its value accessor API?
Angular uses default class implementations for the native elements: https://angular.io/api/forms/ControlValueAccessor#class-implementations
An input
uses the DefaultValueAccessor. It also has a ngDefaultControl
selector.
This value accessor is used by default for<input type="text">
and<textarea>
elements, but you could also use it for custom components that have similar behavior and do not require special processing. In order to attach the default value accessor to a custom element, add the ngDefaultControl attribute as shown below.<custom-input-component ngDefaultControl [(ngModel)]="value"></custom-input-component>
Related Topics
Parse String to Date with Moment.Js
Write Elements into a Child Iframe Using JavaScript or Jquery
Unchecked Runtime.Lasterror While Running Tabs.Executescript
How to Pass JavaScript Variables as Parameters to Jsf Action Method
Sending Binary Data in JavaScript Over Http
How to Send Data in Request Body with a Get When Using Jquery $.Ajax()
How to Sum the Values of a JavaScript Object
Finding "Line-Breaks" in Textarea That Is Word-Wrapping Arabic Text
Detect If an Element Is Visible with Jquery
Javascript: Cancel/Stop Image Requests
Updating Svg Element Z-Index with D3
$(Document).Ready(Function() VS $(Function(){
Add Two Functions to Window.Onload
How to Get Month and Date of JavaScript in 2 Digit Format
Adding Csrftoken to Ajax Request
Alternative or Polyfill for Array.From on the Internet Explorer
Why Does If("String") Evaluate "String" as True But If ("String"==True) Does Not