Two way binding in reactive forms
Asked Answered
G

9

92

Using Angular 2, two-way binding is easy in template-driven forms - you just use the banana box syntax. How would you replicate this behavior in a model-driven form?

For example, here is a standard reactive form. Let's pretend it's much more complicated than it looks, with lots and lots of various inputs and business logic, and therefore more appropriate for a model-driven approach than a template-driven approach.

    export class ExampleModel {
        public name: string;
        // ... lots of other inputs
    }

    @Component({
        template: `
            <form [formGroup]="form">
                <input type="text" formControlName="name">
                ... lots of other inputs
            </form>

            <h4>Example values: {{example | json}}</h4>
        `
    })
    export class ExampleComponent {
        public form: FormGroup;
        public example: ExampleModel = new ExampleModel();

        constructor(private _fb: FormBuilder) {
            this.form = this._fb.group({
                name: [ this.example.name, Validators.required ]
                // lots of other inputs
            });
        }

        this.form.valueChanges.subscribe({
            form => {
                console.info('form values', form);
            }
        });
    }

In the subscribe() I can apply all sorts of logic to the form values and map them as necessary. However, I don't want to map every input value from the form. I just want to see the values for the entire employee model as it updates, in a approach similar to [(ngModel)]="example.name", and as displayed in the json pipe in the template. How can I accomplish this?

Gravestone answered 7/11, 2016 at 6:2 Comment(1)
See the correct answer here #51343275Hippy
B
81

Note: as mentioned by @Clouse24, "Using Reactive Froms with ngModel is deprecated in angular 6 and will be removed in a future version of Angular" (which means that the answer below will no longer be supported in the future). Please read the link to see the reasoning for deprecation and to see what alternatives you will have.

You can use [(ngModel)] with Reactive forms.

template

<form [formGroup]="form">
  <input name="first" formControlName="first" [(ngModel)]="example.first"/>
  <input name="last" formControlName="last" [(ngModel)]="example.last"/>
</form>

component

export class App {
  form: FormGroup;
  example = { first: "", last: "" };

  constructor(builder: FormBuilder) {
    this.form = builder.group({
      first: "",
      last: ""
    });
  }
}

Plunker

This will a completely different directive than the one that would be used without the formControlName. With reactive forms, it will be the FormControlNameDirective. Without the formControlName, the NgModel directive would be used.

Burglary answered 7/11, 2016 at 6:49 Comment(11)
asked a similar question in Angular/Angular and two of the core developers told me that mixing ngModel and Reactive forms is a bad idea. You should be able to get the values with {{this.form.get('first').value}}Polito
@Polito but how can I bin my local modeldata direct to my form so I've two way databinding for my modeldata? Because without the above solution I need to map my formdata back to my modeldataEastern
angular.io/guide/reactive-forms In keeping with the reactive paradigm, the component preserves the immutability of the data model, treating it as a pure source of original values. Rather than update the data model directly, the component extracts user changes and forwards them to an external component or service, which does something with them (such as saving them) and returns a new data model to the component that reflects the updated model state.Polito
peeskillet, you can shoot you to a foot too, but it's no good thing :)Regazzi
Without using ngModel you can access the data as mentioned by @zuriel . Created a plunker. plnkr.co/edit/pPjI4hXI13AfcqOMuVfF?p=previewMichaelamichaele
I thoguht it was bad practice to use ngModel on reactive forms, maybe I am wrong; but in my use case I found it better since I need to update some UI on the fly.Floatable
Using Reactive Froms with ngModel is deprecated in angular 6 and will be removed in angular 7. angular.io/api/forms/FormControlName#use-with-ngmodelMoire
So, in a nutshell: Two-way binding does not work with FormBuilder? (i.e. updating the model automatically when the formControl value changes)Underling
how to do this if we cant use ngmodel? can anyone help?Armchair
Anyone know the official way to implement two-way binding in reactive forms that will be safe in Angular 7?Goutweed
You need to consider that the creators of Angular, other than obviously being talented developers, have a very good reason for why the Reactive Forms are designed the way they are. It is a paradigm shift away from legacy binding. Only when the form data itself is perfect should the user then, during submission to the data store, bind the values from the form to the data model. This is a pure separation of concerns, the 'S' in SOLID. Use Object.assign(dataModel, formGroup.value) to transcode the form values to the data model. Extend a view model from each data model to add view-only fields.Dandrea
D
24

Sometimes you might need to combine [(ngModel)] with Reactive forms. I could be some inputcontrol that you don't need as a part of the form, but you still need it to be binded to the controller. Then you can use: [(ngModel)]="something" [ngModelOptions]="{standalone: true}"

Darrow answered 20/10, 2017 at 7:32 Comment(6)
This has been deprecated in Angular v6 and will be remove in Angular v7. You can check this for more details next.angular.io/api/forms/FormControlName#use-with-ngmodelUtoaztecan
I'm happy to report that this is working in Angular 9. I'm hoping they've changed their mind and will keep this.Christy
@Christy no, it's still deprecated, look at your console. It looks like you're using ngModel on the same form field as formControlName. Support for using the ngModel input property and ngModelChange event with reactive form directives has been deprecated in Angular v6 and will be removed in a future version of Angular.Lenlena
@Shadoweb, that's what it says, but I think they should reconsider. There are cases where binding isn't being used to support the form, and taking away this ability would lead to unnecessarily circumlocution. Restrictions like this are what you'd call unorthogonal, and they make frameworks and languages inelegant and cumbersome.Christy
Angular 13 and it still can be used.Rosenfeld
Angular 15 and it's still there.Uela
S
17

Here is how you can solve it:

In order to have the result of two-way-binding

I use local "template variables" and use the same formControl for both fields.

<form [formGroup]="formGroup">
  <input #myInput (input)="mySlider.value = myInput.value" type="number" formControlName="twoWayControl">

  <mat-slider #mySlider (input)="myInput.value = mySlider.value" formControlName="twoWayControl" min="1" max="100">
  </mat-slider>

</form>

When I programmatically want to change the value of the model I use setValue() as others have proclaimed.

setTo33() {
  this.formGroup.get('twoWayControl').setValue(33);
}
Sather answered 19/6, 2019 at 12:35 Comment(2)
This works for me! Since Angular 7 reactive forms doesn't support ngModelPopish
For Angular devs reading this. I wasn't confused about the ngModel and formControlName! This was super handy actually, making a reactive form and binding a model. What if we could do both in one thing? Something like this: formGroup = new FormGroup({ name: new FormControl(['Michelangelo', {modelBinding: true}]) }) So we can actually bind it to a model and not manually update it.Brendanbrenden
P
5

An Angular 6+ solution...

I too would like reactive form validation while also using two-way data binding. The best solution I've come up with was to hook the form group's valueChanges event with a debounce timer to update the model. Here's an example:

<form [formGroup]="form">
  <input class="form-control" type="date" name="myDate" formControlName="myDate">
</form>
public myModel = {
  myDate: '2021-01-27'
};

public form = this.builder.group({
  myDate: [this.myModel.myDate, [Validators.required]],
});

// Don't update the model with every keypress, instead wait 1s and then update
this.form.valueChanges.pipe(debounceTime(1000)).subscribe((changes) => {
  for (let key of Object.keys(changes)) {
    this.myModel[key] = values[key];
  }
});

To better help copy/pasta I'm going to update the value of all properties of the moodel with the given changes. If you only wanted to update one property with two-way data binding you should use something like:

this.form.get('myDate').valueChanges.pipe(debounceTime(1000)).subscribe((changes) => {
  this.myModel.myDate = changes.myDate;
});
Pino answered 28/1, 2021 at 3:26 Comment(1)
I really like this. I'm just wondering why updating the model is not automatic like in template driven forms? It almost seems like they don't want us not to do this or it would happen by default. And they often point us towards Reactive Forms as a best practice not Template driven forms, reinforcing that they don't want us to do this. Plus removing the ngModel from Reactive Forms. I read the deprecation document, which said it is because it is confusing. But it seems so much more convenient to me.Lodmilla
S
4
    // Allow two way binding on the [(name)] from the parent component
    private nameValue: string;
    @Input()
    get name() {
        return this.nameValue;
    }
    set name(values) {
        this.nameValue = values;
        this.nameChange.emit(this.nameValue);
    }
    @Output() nameChange = new EventEmitter<string>();

    ngOnInit() {
        // Update local value and notify parent on control value change
        this.formControl.valueChanges.forEach(value => this.name = value));
    }

    ngOnChanges() {
        // Update local value on parent change
        this.formControl.setValue(this.expression);
    }
Septilateral answered 15/5, 2019 at 15:37 Comment(0)
O
2

If you just want to show a input value just create a variable in your input and use in your template.

<form [formGroup]="form">
  <input type="text" formControlName="name" #name>
  ... lots of other inputs
</form>

<h4>Example values: {{ name.value }}</h4>
Orville answered 10/9, 2018 at 20:39 Comment(1)
To clarify, this technique is a pure binding to the FormControl itself, and not to the model.Doubleedged
H
0

ngModel or Template driven forms and reactive forms( model driven forms ) can be mixed together. for example, it's easy to read data without subscription when you use TDF and on the other hand, you can provide some validations using MDF. But i would prefer to choose only one of them.

The biggest disadvantage of TDF is that you can't apply unit tests on them and on the other hand it's much dirtier the template when you use TDF.

Haga answered 25/11, 2019 at 16:24 Comment(0)
G
0

You can achieve two way binding by using Reactive forms

constructor(private fb: FormBuilder)

this.formData= fb.group({
        variable: new FormControl(value,Validators.required)
      })
      
      //the 'value' attribute carries the value you want to bind
var value="Eamanpreet Singh"

<form [formGroup]="formData" (ngSubmit)="submit();">
      <mat-form-field>
            <input matInput placeholder="Name" formControlName="variable">
      </mat-form-field>
Granth answered 27/8, 2021 at 5:59 Comment(0)
B
0

Do not use ng model in reactive form cause it is deprecated. Suppose if you have html control which is not a part of form group, we can use simple includeinactive = new formControl() in ts file and html inside form in html control we can use [formControl] = includeinactive. If we want to disable update value we can use patchvalue or setvalue to update it. It will not log any error or warnings in developers tool console also.

Bipinnate answered 19/4 at 1:41 Comment(1)
you can use [(ngModel)] inside a formGroup to a variable that not relation with the FormGroup. The only is add in input ngModelOptions={standalone:true}: <input [(ngMode)]="value" [ngModelOptions]="{standalone:true}"> and this is not deprecated. The "deprecated" is that a long time ago in a galaxy far, far away you could use in the same input formControlName and [ngModel]. This was confussed and unnecessary -the correct way is give value to the FormControl-Regazzi

© 2022 - 2024 — McMap. All rights reserved.