Search code examples
angularangular-reactive-forms

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>

Solution

  • 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