Javascript
What is ngDefaultControl in Angular
Angular, a robust framework for building dynamic web applications, offers various tools to simplify form handling. One such tool, often overlooked but incredibly useful, is ngDefaultControl. So, what is ngDefaultControl in Angular, and why should you care? This directive plays a crucial role in streamlining form control creation, especially when dealing with custom form controls or when you want to enforce a specific control type across your application. It essentially bridges the gap between native HTML elements and Angular’s form control system, ensuring seamless integration and simplifying the overall form development process. Understanding and leveraging ngDefaultControl can lead to cleaner, more maintainable Angular forms, saving you time and effort in the long run. It is a powerful tool for managing form controls, particularly in complex applications with diverse input types. This directive significantly enhances the development experience by automating the creation of form control instances, reducing boilerplate code and improving overall application maintainability.
Understanding the Basics of ngDefaultControl
ngDefaultControl is a directive in Angular that automatically creates a FormControl instance for native HTML form elements like <input>, <select>, and <textarea> when they are used within an ngModel directive and are not already associated with a FormControl. Essentially, it’s a convenience feature that simplifies form creation by implicitly handling the instantiation of form controls. This is particularly helpful when you want to avoid manually creating FormControl instances for every form element, which can become tedious in larger forms. It provides a clean and concise way to manage form elements, especially when you need to quickly prototype or build simple forms.
Without ngDefaultControl, you would typically need to explicitly create a FormControl instance in your component and bind it to the HTML element using the formControl directive. This involves writing more code and managing the lifecycle of the FormControl manually. However, with ngDefaultControl, Angular takes care of this for you automatically, reducing the amount of code you need to write and simplifying the form creation process. It’s important to note that ngDefaultControl only kicks in when an element is not already associated with a FormControl, allowing you to maintain control over specific elements when needed.
The directive automatically applies itself to elements that use ngModel and don’t already have a formControl or formControlName directive. This implicit behavior can significantly reduce boilerplate code, leading to a more streamlined and maintainable codebase. It’s important to understand the precedence of directives: formControl and formControlName take precedence over ngModel and thus prevent ngDefaultControl from creating a new FormControl. This allows for fine-grained control over your form elements.
Benefits of Using ngDefaultControl
There are several key benefits to utilizing ngDefaultControl in your Angular projects. First and foremost, it reduces boilerplate code. By automatically creating FormControl instances, you avoid the need to manually instantiate and bind form controls, resulting in cleaner and more concise component templates. This simplification can significantly speed up development time, especially when dealing with forms that contain numerous input fields. It also makes the code easier to read and understand, improving maintainability.
Secondly, ngDefaultControl improves code maintainability. With less code to write and manage, your component templates become easier to understand and modify. This is particularly beneficial in larger projects where code complexity can quickly become a bottleneck. By reducing the cognitive load associated with form management, developers can focus on other aspects of the application, leading to higher productivity and fewer errors. Furthermore, the automatic creation of form controls ensures consistency across your forms, reducing the risk of inconsistencies and bugs.
Finally, it simplifies form prototyping. When rapidly iterating on form designs, ngDefaultControl allows you to quickly add form elements without worrying about the underlying FormControl instantiation. This speeds up the prototyping process and allows you to focus on the user interface and user experience. For instance, you can quickly add input fields and bind them to ngModel without the overhead of manually creating and binding FormControl instances. This allows for faster experimentation and refinement of form designs.
- Reduces boilerplate code
- Improves code maintainability
- Simplifies form prototyping
Practical Examples of ngDefaultControl in Action
Consider a simple registration form with fields for name, email, and password. Without ngDefaultControl, you would need to manually create a FormControl for each field in your component and then bind it to the corresponding HTML element. This would involve writing code like this:
// In your component.ts name = new FormControl(''); email = new FormControl(''); password = new FormControl(''); // In your component.html <input type="text" [formControl]="name"> <input type="email" [formControl]="email"> <input type="password" [formControl]="password">
However, with ngDefaultControl, you can simplify this significantly:
// In your component.ts name: string = ''; email: string = ''; password: string = ''; // In your component.html <input type="text" [(ngModel)]="name"> <input type="email" [(ngModel)]="email"> <input type="password" [(ngModel)]="password">
As you can see, ngDefaultControl eliminates the need to explicitly create FormControl instances, making the code much cleaner and easier to read. In a more complex scenario, imagine creating a dynamic form where the number of fields is determined at runtime. Using ngDefaultControl can greatly simplify the process of adding and removing form controls, as you don’t need to manually manage the creation and destruction of FormControl instances. Simply bind the input fields to ngModel, and Angular will take care of the rest. This is particularly useful when dealing with forms that require a high degree of flexibility and adaptability. Angular’s documentation provides further examples of how ngDefaultControl streamlines form development here.
When to Use and When to Avoid ngDefaultControl
ngDefaultControl is most useful when you are working with simple forms where you don’t need fine-grained control over the FormControl instances. It’s also a great option for rapid prototyping or when you want to quickly add form elements without worrying about the underlying form control management. For example, if you’re building a simple contact form or a basic login form, ngDefaultControl can significantly simplify the development process.
However, there are situations where you should avoid using ngDefaultControl. If you need to customize the validation rules or behavior of a specific form control, or if you need to access the FormControl instance directly, you should explicitly create a FormControl instance and bind it to the HTML element using the formControl directive. This gives you more control over the form control and allows you to implement complex validation logic or custom behavior. According to research, explicit control over form elements is vital for ensuring data accuracy and preventing errors [Nielsen Norman Group].
In summary, use ngDefaultControl for simple forms and rapid prototyping, and avoid it when you need fine-grained control or custom validation logic. Here’s a general guideline:
- Assess the complexity of your form.
- Determine if you need custom validation or behavior.
- If the form is simple and doesn’t require custom behavior, use
ngDefaultControl. - If the form is complex or requires custom behavior, create explicit
FormControlinstances.
The key difference between ngDefaultControl and formControl/formControlName lies in how form controls are managed. ngDefaultControl implicitly creates a FormControl instance, while formControl and formControlName require you to explicitly create and manage the FormControl instances in your component. formControl binds directly to a FormControl instance, whereas formControlName links to a FormControl within a FormGroup.
formControl is used when you want to directly bind a FormControl instance to an HTML element. This gives you complete control over the form control and allows you to customize its behavior and validation rules. formControlName, on the other hand, is used when you are working with a FormGroup. It allows you to bind an HTML element to a specific FormControl within the FormGroup. This is useful when you want to group related form controls together and manage them as a single unit. Consider a scenario where you have multiple input fields that represent an address. You can group these fields together using a FormGroup and then use formControlName to bind each input field to the corresponding FormControl within the FormGroup.
The featured snippet-optimized paragraph: To summarize, ngDefaultControl is a convenience directive that simplifies form creation by automatically creating FormControl instances for native HTML form elements when they are used with ngModel and are not already associated with a FormControl. formControl and formControlName, conversely, require explicit creation and management of FormControl instances, providing greater control and flexibility but also requiring more boilerplate code. Understanding these distinctions is essential for choosing the right approach for your specific form development needs. You can read more about the nuances of Angular forms here.
ngDefaultControl: Implicitly createsFormControlinstances.formControl: Binds directly to aFormControlinstance.formControlName: Links to aFormControlwithin aFormGroup.
FAQ: Frequently Asked Questions about ngDefaultControl
- **Q: Does `ngDefaultControl` work with custom form controls?**
- A: No, `ngDefaultControl` only works with native HTML form elements like ``, `
- **Q: Can I use `ngDefaultControl` with reactive forms?**
- A: `ngDefaultControl` is primarily designed for template-driven forms using `ngModel`. While it can technically be used with reactive forms, it's generally recommended to use `formControl` or `formControlName` for reactive forms to maintain consistency and control.
- **Q: What happens if I use both `ngDefaultControl` and `formControl` on the same element?**
- A: `formControl` takes precedence over `ngDefaultControl`. The `FormControl` instance bound to the `formControl` directive will be used, and `ngDefaultControl` will be ignored.
- **Q: Is `ngDefaultControl` necessary for all forms in Angular?**
- A: No, `ngDefaultControl` is optional. It's a convenience feature that simplifies form creation, but it's not required. You can always explicitly create `FormControl` instances and bind them to the HTML elements using the `formControl` directive if you prefer.
Now that you have a solid grasp of what ngDefaultControl is and how it Question & Answer :
No, this is not a duplicate question. You see, there is a ton of questions and issues in SO and Github that prescribe that I add this directive to a tag that has [(ngModel)] directive and is not contained in a form. If I don’t add it I get an error:
ERROR Error: No value accessor for form control with unspecified name attribute
Ok, the error goes away if I put this attribute there. BUT, wait! Nobody knows what it does! And Angular’s doc doesn’t mention it at all. Why do I need a value accessor when I know that I don’t need it? How is this attribute connected to value accessors? What does this directive do? What is a value accessor and how do I use it?
And why does everybody keep doing things that they don’t understand at all? Just add this line of code and it works, thank you, this is not the way to write good programs.
And then. I read not one but two huge guides about forms in Angular and a section about ngModel:
- https://angular.io/guide/forms
- https://angular.io/guide/reactive-forms
- https://angular.io/guide/template-syntax#ngModel
And you know what? Not a single mention of either value accessors or ngDefaultControl. Where is it?
[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 {}
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.

