Sabbir Rahman
Sabbir Rahman

Reputation: 1189

How to dynamically change the bound formControlName

I have an angular 2 reactive form with four formControls and only one input field. What I want is to ask the user to fill up the infos one by one. So I'm assigning the firstControlName to a property call currentFormControlName on ngOnInit and binding it with the input field in template file. When the user fills up his name the field will be valid and on submit I'll change the currentFormControlName property to next formControlName. But the problem is the binding isn't updating. The input field is still bound to name. When I type something on the input field the value of name is updating, not email.

app.component.ts

ngOnInit() {
  this.form = this.builder.group({
    'name': ['', Validator.required],
    'email': ['', Validator.email],
    'phone': ['', Validator.required],
    'password': ['', Validator.required],
  });
  this.currentFormControlName = 'name';
}

submit() {
  this.currentFormControlName = 'email'; // Setting it manually just for the demo of this question.
}

app.component.html

<form [formGroup]="form">
  <input type="text" [formControlName]="currentFormControlName">
  <input type="submit" (click)="submit()">
</form>

Upvotes: 3

Views: 22546

Answers (3)

Hemant Pandey
Hemant Pandey

Reputation: 71

You can use this Approach: Simply combine specific control with form-control because at the end formControlName also resolves into FormControl

<form [formGroup]="form" #formDir="ngForm">
<input type="text" [formControl]="currentFormControl">
<input type="submit" (click)="submit()">
{{ form.value | json }}

in TS

 currentFormControl: FormControl;

 ngOnInit(){
 this.currentFormControl = this.form.controls.name;
 }
 submit(){
  this.currentFormControl = this.form.controls.email;
 }

Upvotes: 0

yurzui
yurzui

Reputation: 214017

Update

You can also use FormControlDirective to switch between controls

[formControl]="form.get(currentFormControlName)"

Old Answer

Let's say we have the following

template.html

<form [formGroup]="form" #formDir="ngForm">
    <input type="text" #controlDir [formControlName]="currentFormControlName">
    <input type="submit" (click)="submit()">
</form>
<pre>{{ form.value | json }}</pre>

After clicking on submit button we can change currentFormControlName and register control with new name like

component.ts

form: FormGroup;

@ViewChild('formDir') formDir: FormGroupDirective;
@ViewChild('controlDir', { read: FormControlName }) controlDir: FormControlName;

currentFormControlName: string;

constructor(private builder: FormBuilder) {}

ngOnInit() {
    this.form = this.builder.group({
        'name': ['', Validators.required],
        'email': ['', Validators.email],
        'phone': ['', Validators.required],
        'password': ['', Validators.required],
    });
    this.currentFormControlName = 'name';
}

submit() {
    this.formDir.removeControl(this.controlDir);
    this.controlDir.name = this.currentFormControlName = 'email'
    this.formDir.addControl(this.controlDir);
}

After that our input element will manage email value. So if we type something in input it will be reflected in form.email value

Plunker Example

This solution is based on FormControlName source code

ngOnChanges(changes: SimpleChanges) {
  if (!this._added) this._setUpControl();
  if (isPropertyUpdated(changes, this.viewModel)) {
    this.viewModel = this.model;
    this.formDirective.updateModel(this, this.model);
  }
}

we can see this directive registers control only once. But it also have the following ngOnDestroy hook

ngOnDestroy(): void {
  if (this.formDirective) {
    this.formDirective.removeControl(this);
  }
}

that gave me some idea

Plunker Example

Upvotes: 11

Max Koretskyi
Max Koretskyi

Reputation: 105449

ngFormNameDirective

You can't do that because ngFormControlName directive uses @Input() name only once here:

@Directive({selector: '[formControlName]'...})
export class FormControlName extends ... {
  @Input('formControlName') name: string;

  ngOnChanges(changes: SimpleChanges) {
    if (!this._added) this._setUpControl(); <------------ here
    if (isPropertyUpdated(changes, this.viewModel)) {

On each subsequent change this._added will be true. The next check you can see in ngOnChanges is isPropertyUpdated which doesn't check name input change and only checks model input:

export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean {
  if (!changes.hasOwnProperty('model')) return false;
  const change = changes['model'];

ngFormDirective

To do what you're trying to do you need to use ngFormDirective which does check that control is updated:

export class FormControlDirective extends NgControl implements OnChanges {
  viewModel: any;

  @Input('formControl') form: FormControl;

  ngOnChanges(changes: SimpleChanges): void {
    if (this._isControlChanged(changes)) {

  private _isControlChanged(changes: {[key: string]: any}): boolean {
    return changes.hasOwnProperty('form');
  }

However, this is a standalone directive.

Upvotes: 2

Related Questions