What Is Ngdefaultcontrol in Angular

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 API
and a native element in the DOM.

Let's write the following template in simple angular2 application:

<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 to
'ngModel' since it isn't a known property of 'input'.

Okay, we can open SO and get the answer: import 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 {}

Sample Image

After some investigation we can discover that three directives will be applied to our input

  1. NgControlStatus

    @Directive({
    selector: '[formControlName],[ngModel],[formControl]',
    ...
    })
    export class NgControlStatus extends AbstractControlStatus {
    ...
    }

  2. NgModel

    @Directive({
    selector: '[ngModel]:not([formControlName]):not([formControl])',
    providers: [formControlBinding],
    exportAs: 'ngModel'
    })
    export class NgModel extends NgControl implements OnChanges,

  3. 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:

Sample Image

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 component

I 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:

  1. What is ngDefaultControl in Angular?

  2. 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



Leave a reply



Submit