Two way binding in reactive forms

2020-02-26 14:24发布

问题:

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?

回答1:

Note: as mentioned by @Clouse24, "Using Reactive Froms with ngModel is deprecated in angular 6 and will be removed in angular 7" (which means that the answer below will no longer be supported beginning version 7). 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.

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

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.



回答2:

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}"



回答3:

Here is how you can solve it:

  • shortcut --> STACKBLITZ

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);
}


回答4:

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

... lots of other inputs
    <h4>Example values: {{ name.value }}</h4>


回答5:

// 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);
}


回答6:

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.