Angular4 forms - formGroup setValue to model with extra fields
Asked Answered
B

4

8

I have a form to update just part of the field of an existing model class in angular 4.

I need to setValue on the current model instance and at the end get the value back updated.

The issue is I need to update only parts of the model but the angular forms returns an error

"Cannot find form control with name 'fieldname'" 

for the extra fields that I do not want to edit.

What is my best option to get the needed behavior?

I thought about adding hidden formControls for the fields I do not want to update in the forms but some of our models are pretty big while we need the user to only edit parts of them.

I have to get an updated model at the end for dirty checking and generic updating of our models (compared to having multiple duplicate class relevant code just for setting the values of all the formControls and updating the models back from the formControls on submit)

Thanks.

UPDATE just for clarification - I want a behavior that is similar to this:

--component.ts
model: Model = someModelWithData;

--component.html
<input type="checkbox" [(NgModel)]="model.FieldOne" />

After the user changes the value in FieldOne, the model is still full with all other data but with FieldOne changed.

Bulbil answered 12/4, 2018 at 13:1 Comment(6)
If you want to handle the state of the whole form automatically, you have to define and instantiate the exact model of your form. You can not bind the form fields to what ever object fields you want. You have to decide whether you implement a template-driven or reactive form. If you need to bind form fields to nested object properties, I suggest you implement the reactive form, in other words the FormControls you mentioned.Thisbe
Its possible I didn't understand correctly but just to clarify, your answer is to use the behavior I said I do not want? Creating a custom model and mapping back and forth from my original model is not a good way of handling the data as it creates extra useless code that in this question I am trying to avoid.Bulbil
Template-driven form is based on ngForm directive and reactive form is based on FormGroup and FormControl instances. None of them support direct binding to a complex object of your own. It really is the best practise to get the values from the form model to your model like this.myModel.fieldChecked = this.formModel.fieldChecked.Thisbe
No idea why that is a best practice, I try to write meaningful code, not copy pasted code, it is a bad practice to have a lot of copy pasted meaningless code for many reasons...Thank you for the answer tho.Bulbil
It's the best practise, because then you are using the Angular form features the way they are originally designed to and you will face less problems in the future as your application grows more complex. In the form model you can also validate the values before moving them to other application models. The only (bad) way you can hack this is to use plain HTML form, prevent the default submit event with JavaScript and handle the input elements binding individually. Less code is not equal to better solution in production applications.Thisbe
Having hundreds of line of code of useless mapping is prone to errors, maintenance difficulty and all sorts of fun stuff. obviously you would want to use the library as intended - as for this question to see if there's a legitimate way to make it work without all this extra code. Less code is not equal to best solution, but so does More code - I always want to have less code if possible. Thanks tho.Bulbil
S
11

You can update parts of your form with the method patchValue instead of setValue.

this.yourForm.patchValue({
     yourFieldToUpdate: yourValue
});
Spectrum answered 12/4, 2018 at 13:3 Comment(6)
Yes, but i cannot get an updated model back this wayBulbil
When do you want your model back ? Maybe show us the code ?Spectrum
I want to set the full model and get the updated model after edit, the code is pretty simple. I want to avoid having code about for specific fields of specific models when the framework should take care of the update of the model.Bulbil
So I don't understand because you say "I need to update only parts of the model" and then "I want to set the full model"Spectrum
I want to get the full model back, with the changed parts, I added some code to clarify.Bulbil
You do not always want the user to change ALL the fields of a model (actually most times you want him to change only parts of it)Bulbil
C
5

Angular is built in such a way to keep the form model and data model separate.

From the documentation:

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.

But if you want to bind to a data model inside of a form, you can set ngModelOptions standalone to true

standalone: Defaults to false. If this is set to true, the ngModel will not register itself with its parent form, and will act as if it's not in the form. This can be handy if you have form meta-controls, a.k.a. form elements nested in the tag that control the display of the form, but don't contain form data.

Stack Blitz Example

In this example, the model has 3 fields, 2 of which can be edited on the form.

model.ts

export class Model {
  fieldOne: string;
  fieldTwo: string;
  fieldThree: string;

  constructor(fieldOne: string, fieldTwo: string, fieldThree: string)
  {
    this.fieldOne = fieldOne;
    this.fieldTwo = fieldTwo;
    this.fieldThree = fieldThree;
  }
}

app.component.ts

import {Component, OnInit} from '@angular/core';
import {AbstractControl, FormBuilder, FormGroup} from '@angular/forms';

import {Model} from './model';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  private _form
  private _model: Model;

  get form(): FormGroup {
    return this._form;
  }

  get model(): Model {
    return this._model;
  }

  set model(model: Model) {
    this._model = model;
  }

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit() {
    this.model = this.newModel();
    this.createForm();
  }

  private newModel(): Model {
    return new Model('fieldOne', 'fieldTwo', 'fieldThree');
  }

  private createForm() {
    this._form = this.formBuilder.group({
    });
  }
}

app.component.html

As the form values change, the actual data model is updated and only editable fields have been exposed to change via the form.

<form [formGroup]="form">
  <div>
    <label for="fieldOne">Field One</label>
    <input
      id="fieldOne"
      type="text"
      [(ngModel)]="model.fieldOne"
      [ngModelOptions]="{ standalone: true }">
  </div>

  <div>
    <label for="fieldTwo">Field Two</label>
    <input
      id="fieldTwo"
      type="text"
      [(ngModel)]="model.fieldTwo"
      [ngModelOptions]="{ standalone: true }">
  </div>
</form>

<h3>Model</h3>
<pre>{{ model | json }}</pre>
Counselor answered 14/4, 2018 at 23:13 Comment(4)
Your example shows how much extra code is needed for this simple task, this is exactly why I asked this question - I am trying to avoid writing so much useless code for each and every model we have. I prefer writing meaningful code with actual needed logic :/ . So your answer is - that's how it is and there's no way around this behavior?Bulbil
@RoyiMindel The answer I posted thought you needed a way to bind reactive form controls to the original data model as the user entered them. I apologize for the confusion. There is a another way using ngModelOptions. I updated the answer and the Stack Blitz example. I hope that helps. :)Counselor
This is a strange mix of Reactive and TemplateDriven, thank you for trying to help but disconnecting the control from the form sounds strange, I might as well use only template-driven if the reactive form is empty. why use it at all?Bulbil
Exactly. The intention of ngModelOptions is for meta data or other controls that don't contain form data but need to be inside the formGroup. If the fields need to be tied to the form via reactive controls, then valueChanges is the way to go. Reactive forms keep the form and data model separate by design. Of course, there could be another solution out there that I haven't run across. These are the two that I'm aware of. Perhaps someone else has another idea.Counselor
P
2

What about binding two ways to ONLY the controls whose fields (in Model) you want to update and setting values to the rest using angular double curly expressions

Eg.

<input type="checkbox" [(NgModel)]="model.FieldOne">
<input type="text" name="fieldone" [(NgModel)]="model.FieldOne">
<input type="text" name="fieldtwo"[(NgModel)]="model.FieldTwo">
<input type="text" name="ignore1" value ="{{model.Ignore1}}">
<input type="hidden" name="ignore2" value ="{{model.Ignore2}}">
Pryce answered 18/4, 2018 at 15:16 Comment(0)
E
1

Here is a fairly easy way to create a new object (model) that can be bound to your form controls.

let model: any = {};

for (let key in this.form.controls) {
  model[key] = this.account[key];
}

this.form.setValue(model);

this.account = object that was retrieved from external source.

this.form is the FromBuilder group of FormControl.

This way the user will be editing a copy of your entity, and upon saving you can do validation/compare between original and user input, if needed.

const entity = this.form.getRawValue();

You also have the option of merging the existing account object with the model that was bound to user input into a new object, that will contain fields that users are not allowed to edit and what they have just edited.

const entity = { ...this.account, ...this.form.getRawValue() };
Electrotype answered 7/4, 2022 at 23:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.