4

I am new to angular 5 so basically am still grasping with the concepts. Using Angular's documentation on reactive forms as an example (https://angular.io/guide/reactive-forms), the following code is as given:

<div formArrayName="secretLairs" class="well well-lg">
<div *ngFor="let address of secretLairs.controls; let i=index" 
 [formGroupName]="i" >
    <!-- The repeated address template -->
  </div>
  </div>

What does secretlairs.controls mean and what is it? According to angular it means that:

The source of the repeated items is the FormArray.controls, not the FormArray itself. Each control is an address FormGroup, exactly what the previous (now repeated) template HTML expected.

Does secretlairs.controls contain any data? Can i replace this portion with lets say an object itself with data of type any and instantiated with data obtained from webapi? For example, instead of

*ngFor="let address of secretLairs.controls

i use

*ngFor="let address of addresses

where addresses is of type any and data obtained from database.

1 Answer 1

7

First, there are three type of forms - FormControl, FormGroup, and FormArray - all inherit from AbstractControl.

When you use Reactive Forms for validation and include either formGroupName, formControlName, or formArrayName in the component's template, you are actually declaratively defining a map between the form control tree and the root FormGroup model.

For example, given the following template:

<div [formGroup]="formGroup">
    <div formGroupName="personalInfo">
         First Name: <input type="text" formControlName="firstName"><br />
         Last Name: <input type="text" formControlName="lastName"><br />
    </div>
    <div formArrayName="cities">
        Top cities: <input *ngFor="let city of cities; index as i" type="text" [formControlName]="i">
    </div>
</div>

You are declaratively setting up a form map for collecting information, which will eventually produce a JSON object in a specific format. For example, given the above form model, formGroup.value would return:

{
   "personalInfo": {
        "firstName: 'John',
        "lastName: 'Smith'
    },
    "cities": [
        "New York",
        "Winnipeg",
        "Toronto"
    ]
 }

Once you've declared the structure of your form group in your template, you need to imperatively create the corresponding formGroup, formControl, and formArray in your component class. As you setup each form, you have the opportunity to set up additional parameters:

  1. Initial Form Value
  2. Array of synchronous validators
  3. Array of asynchronous validators

This applies to any of the abstract controls.

This is what the corresponding formGroup model would look like given the above template:

export class AppComponent {
  firstName: string,
  lastName: string;
  cities: string[];
  @Input() formGroup: FormGroup;
  constructor(private fb: FormBuilder) {
    // setup initial values
    this.cities = ['New York', 'Winnipeg', 'Toronto'];
    this.firstName = 'John';
    this.lastName  = 'Smith';

    // create a formGroup that corresponds to the template
    this.formGroup = fb.group({
      firstName: [this.firstName, Validators.required],
      lastName: [this.lastName, Validators.required],
      cities: fb.array(this.cities.map(t=> fb.control(t, Validators.required)))
    })
  }
}

To bind to any of the form's validation flags, you can use the root formGroup and a path string (which supports dots) to find the particular group, control, or array.

For example:

<div *ngIf="formGroup.get('firstName').errors.required">First Name is Required</div>

Hopefully, that makes it clear.

[Edit]

Better yet, given that Reactive Forms is all about declaratively setting up a model in the template, and mapping the same model imperatively in the component class, you should consider defining a JSON model and using that instead.

For example, suppose we have a custom model MyModel which has a firstName property, lastName property, and a cities property. The component class would look like this:

 export class AppComponent {
  @Input() model: MyModel;
  @Output() modelChange: EventEmitter<MyModel>;

  @Input() formGroup: FormGroup;
  constructor(private fb: FormBuilder) {
    this.model = new EventEmitter<MyModel>();
    // create a formGroup that corresponds to the template
    this.formGroup = fb.group({
      firstName: [this.model.firstName, Validators.required],
      lastName: [this.model.lastName, Validators.required],
      cities: fb.array(this.model.cities.map(t=> fb.control(t, Validators.required)))
    });
  }

  onSubmit() {
     if (this.formGroup.valid) {
        this.model = this.formGroup.value;
        this.modelChange.next(this.model);
     }
  }
}
Sign up to request clarification or add additional context in comments.

5 Comments

hi there, thank you for your input. this has definitely made things much clearer on how the whole concept works. However, can you explain to me what formarray.controls specifically refer to? does it each control in the formarray.controls refer to the formgroup? can i replace this part with lets say a data model instead of controls?
The example I gave is a data model (instead of a control model)
i see, so just to point out that it is not necessary to always use the control model right? this means that if i choose to work with a data model, i will still be able to bind the user input to the form fields? ( through the formcontrol names as you mentioned in your example)
yes, that's correct. You can bind to a regular Data model, or a Control model.
this explained the question well. Thank you. I will upvote this as correct answer

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.