1

TL,DR;

Undefined values behave differently depending on the method of achieving two-way binding in Angular Reactive Forms. Please see this StackBlitz for a demo.

enter image description here

Detail

The SO Question, How to correctly 2-way-bind with reactive forms?, received an answer stating...

there's no reason to not mix formControlName and ngModel

Firstly, is this approach a valid way to achieve two-way data binding?

Secondly, can someone clarify the difference in behaviour of undefined values in Angular Reactive Forms? I've created a StackBlitz for this question.

The main HTML form mutates the component's underlying data, attributes:

export class AppComponent implements OnInit {
  public form: FormGroup;

  // title and subtitle will be undefined.
  public attributes = {};

  constructor(private readonly fb: FormBuilder) { }

  public ngOnInit(): void {

    // This is OK, I want title and subtitle to be undefined.
    this.form = this.fb.group({
      title: this.attributes.title,
      subtitle: this.attributes.subtitle
    });
  }
}

The form: FormGroup defines two <input> and <button> pairs; one for attributes.title, another for attributes.subtitle.

The first <input> uses [(ngModel)] - binding to the underlying model with a template driven approach within reactive form:

<!-- Title uses [(ngModel)] for 2-way binding. -->

<input formControlName="title"
       [(ngModel)]="attributes.title">

<button (click)="undefineTitle()">Undefine title</button>

The <button> simply sets attributes.title to undefined:

public undefineTitle() {
    this.attributes.title = undefined;
}

The second <input> uses [value] and (input) rather than [(ngModel)] for data-binding:

<!-- Subtitle uses [value] and (input) for 2-way binding. -->

<input formControlName="subtitle"
       [value]="getValue()"
       (input)="setValue($event.target.value)">

<button (click)="undefineSubtitle()">Undefine subtitle</button>

The <button> simply sets attributes.subtitle to undefined:

public undefineSubtitle() {
    this.attributes.subtitle = undefined;
}

Since attributes.subtitle is modified using [value] and (input) rather than [(ngModel)], two simple event handlers are defined in the component:

/* Handlers for 2-way binding of subtitle */
public getValue(): string {
    return this.attributes.subtitle;
}

public setValue(s: string) {
    this.attributes.subtitle = s;
}

The <input> element shows undefined for subtitle, whereas title seems to handler this better (IMO) and displays nothing?

Upon page-load, you can see the problem immediately since neither title nor subtitle are defined when the form: FormGroup is created:

enter image description here

As expected, two-way data-binding works:

enter image description here

...and empty values behave the same:

enter image description here

Jack
  • 10,313
  • 15
  • 75
  • 118
  • You're using template-driven ngModel field. These aren't reactive forms. – Estus Flask Jan 08 '18 at 21:00
  • So to achieve two-way binding with reactive forms, separate `[value]` and `(input)` directives must be used? – Jack Jan 08 '18 at 21:06
  • I mean that these are 'regular' forms, not reactive. The answer may be different for those two. Reactive are those where form controls are used. – Estus Flask Jan 08 '18 at 21:39
  • With reactive form, you just need `formControlName` (wrapped by some `[formGroup]`), and two-way bindings will be created. Scrap all `[value]`, `(input)` and `[(ngModel)]`. – Harry Ninh Jan 08 '18 at 23:15
  • @harry-ninh - Unfortunately the value must be written to a different part of the model depending on the value of a select-menu. I must capture the input, check a variable, and write to the appropriate part of the model. – Jack Jan 08 '18 at 23:17
  • Do subscribe to `valueChanges` event in your component class and all will be sorted. – Harry Ninh Jan 08 '18 at 23:25

0 Answers0