1
votes

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 on. 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 fill 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 bonded 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>
2
Did you try with this.currentFormControlName = this.form.get('name'); (and use the same method for the mail) ? - user4676340
I don't think it's possible.. Every input need to have a form control witch is defined in the the initialize of the form group. - Gili Yaniv
You could use a ngSwitch in combination of a Subject and every time the submit() is triggered you update the form by showing the nexy Input field - Daniel Hoppe Alvarez

2 Answers

8
votes

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

1
votes

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.