1
votes

i'm trying to create my first Angular 2 application. And as usual i'm instantly in trouble with the model I wanna create. I have a model for creating a parent-child relation between artilces. For example: I wanna add an article to my basket with some child objects as follows:

IArticle[] = [
{
    id: 1,
    parentArticle: 1234596,
    description: 'some description for parent'
    children: [
        {
            id: 1,
            childArticle: 123457,
            childDescription: 'some description for first child'
        },
        {
            id: 2,
            childArticle: 123467,
            childDescription: 'some description for second child'
        },
        .....
    ]

In typescript I defined an interface for my Article. The child objects have their own interface which is slightly different.

Because these child objects are a small fixed set I wanna add these children using a dropdownlist. I want these objects to display as buttons with a removal when clicked (optional with modal popup with "are you sure" question). So I thought the button beside the dropdownlist should add the dropdownlist value to the array. This works fine when I added the values to a normal typed array. But now I wanna wrap this array in a "Reactive Form".

Can someone help me how to work with arrays in these Reactive Forms and how to wrap them in a formcontrol object. I looked at the FormArray "control" but am not sure is this is what i'm searching for. It seams an array of FormControls, not an array of data. Maybe Reactive Forms is not the right decision? Hopefully someone can put me on the right track.

My html template is as follows (bootstrap code stripped from the code)

<form autocomplete="off">
      <input type="text" Placeholder="Article Number" />
    <div >
      <button *ngFor="let child of children">
        <span class="glyphicon glyphicon-remove-circle text-danger"></span>
        {{ child.childArticle }}
      </button>
    </div>
    <div >
        <select class="form-control inline">
          <option value="">Select Service..</option>
          <option *ngFor="let serviceOption of serviceOptions" [ngValue]="serviceOption.serviceNumber">
              {{serviceOption.serviceDescription}} 
          </option>
        </select>
          <button  (click)="onAddService()">
             Add
          </button>
    </div>
    <div>
      <button type="submit">
         Save
      </button>
    </div>
  </form>

Kind Regards.

------ typescript code:

export class NewServiceComponent implements OnInit {
  newServiceForm: FormGroup;
  serviceOptions: IServiceArticle[] = [
     { 'id': 1, 'serviceNumber': 123456, serviceDescription: 'description 1' },
     { 'id': 2, 'serviceNumber': 456789, serviceDescription: 'description 2' },
     { 'id': 3, 'serviceNumber': 123456, serviceDescription: 'description 3' },
];
selectedServiceOption: IServiceArticle;
newArticle: IService;

constructor(private _fb: FormBuilder) {
}

createForm() {
 this.newServiceForm = this._fb.group({
   services: this._fb.array([]),
   articleNumber: this._fb.control(null, Validators.required),
 })
}

ngOnInit() {
  this.createForm();
}

onAddService() {
  const arrayControl = <FormArray>this.newServiceForm.controls['services'];
  let newgroup = this._fb.group({
    serviceNumber: this.selectedServiceOption.serviceNumber,
    serviceDescription: this.selectedServiceOption.serviceDescription,
  })
  arrayControl.push(newgroup);
}
}
2
Just wondering about the select, this could mean that the user could choose the same value over and over?AT82
at this time it could. Validation logic should be updated to avoid that. For my understanding of angular, this isn't a problem at this moment.Luuk Krijnen
Hey, how did it go with the answers? Did either help or do you need further assistance? :)AT82
i come back at it soon. because of a go live i implemented this using the template forms approach. After my vacation i come back at the reactive form approachLuuk Krijnen

2 Answers

0
votes

When creating a reactive form, you first have the main form group and you will have an formArray control inside the form group. After doing that you will add any existing children to that formArray and each of those children will be its own formGroup.

constructor(private fb: FormBuilder){
    myTypeScriptChildrenObjects = functionToGetTheChildrenArray();
}
...

createForm(){
    this.form = this.fb.group({
        childrenArray: this.fb.array([])
    })

    const arrayControl = <FormArray>this.orderForm.controls['childrenArray'];
    this.myTypeScriptChildrenObjects.forEach(item => {
        let newGroup = this.fb.group({
            prop1: [item.prop1],
            prop2: [item.prop2],
            prop3: [item.prop3]
        })
        arrayControl.push(newGroup);
    })
}

When cycling through these in your html you do something like this:

<select formArrayName="childrenArray">
          <option [formGroupName]="i" *ngFor="let line of form.controls['childrenArray'].controls; index as i" text="prop1" value="prop2">
0
votes

Tried the following code and it seemed to work as it should....

As for the form, you'd need to add the [formGroup], so:

<form [formGroup]="newServiceForm">

and since this is inside a formgroup now, it won't accept [(ngModel)] for the select without a form control. We can bypass this with using [ngModelOptions]="{standalone: true}"

also I would then change the [ngValue] to refer to the object: [ngValue]="serviceOption"

Then we add the selected value and push it to the the form array, just like you have done.

In template we now want to iterate this form array. All in all, the template should look something like this:

<form [formGroup]="newServiceForm">
  <div formArrayName="services">
    <button *ngFor="let child of newServiceForm.controls.services.controls; 
         let i = index" [formGroupName]="i">
      <span [innerHtml]="child.controls.serviceDescription.value"> </span>
    </button>
  </div>

 <select [(ngModel)]="selectedServiceOption" [ngModelOptions]="{standalone: true}">
    <option>Select Service..</option>
    <option *ngFor="let serviceOption of serviceOptions" [ngValue]="serviceOption">
          {{serviceOption.serviceDescription}} 
    </option>
 </select>
 <button  (click)="onAddService()"> Add </button>
</form>

The form controls could also be set to variables so that you could avoid those long paths.

And as discussed in comments, this doesn't take in consideration the fact that user can choose same value over and over, but that was not relevant in question.